async def voting_remind_me(self, button: discord.ui.Button, interaction: discord.Interaction): current_reminder = bool(await self.bot.db.query("SELECT to_remind FROM votes WHERE id=$1 LIMIT 1", interaction.user.id)) await self.bot.db.query("INSERT INTO votes (id,to_remind) VALUES ($1,$2) ON CONFLICT(id) DO UPDATE SET to_remind=$3", str(interaction.user.id), not current_reminder, not current_reminder) if current_reminder is not True: await interaction.response.send_message(ephemeral=True, embed=embed(title="I will now DM you every 12 hours after you vote for when you can vote again")) elif current_reminder is True: await interaction.response.send_message(ephemeral=True, embed=embed(title="I will stop DMing you for voting reminders 😢"))
async def norm_patreon_server_true(self, ctx: "MyContext"): guild_id, tier, patreon_user = (await self.bot.db.query( "SELECT id,tier,patreon_user FROM servers WHERE id=$1", str(ctx.guild.id)))[0] if tier is None or patreon_user is None: user_tier = await self.bot.log.fetch_user_tier(ctx.author) # Probably check what server has it and remove it instead of saying the following if user_tier == list( config.premium_tiers )[1] and len(await self.bot.db.query( "SELECT id,tier,patreon_user FROM servers WHERE patreon_user=$1", str(ctx.author.id))) >= 1: return await ctx.reply(embed=embed( title= "You have already used your patronage on another server", color=MessageColors.ERROR)) await self.bot.db.query( "UPDATE servers SET tier=$1, patreon_user=$2 WHERE id=$3", str(user_tier), str(ctx.author.id), str(ctx.guild.id)) # self.bot.log.change_guild_tier(ctx.guild.id, user_tier) await ctx.reply(embed=embed(title="New server activated")) elif patreon_user == ctx.author.id: await ctx.reply(embed=embed( title="You have already activated this server")) else: await ctx.reply(embed=embed( title="There is already a patreon member for this server", color=MessageColors.ERROR))
async def stop(self, ctx: MyContext): """Stop the player and clear all internal states.""" player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, ctx=ctx) if not player.is_connected: return await ctx.send( embed=embed(title='Nothing is playing right now', color=MessageColors.ERROR)) if self.is_privileged(ctx): await ctx.send( embed=embed(title='An admin or DJ has stopped the player.', color=MessageColors.MUSIC)) return await player.teardown() required = self.required(ctx) player.stop_votes.add(ctx.author) if len(player.stop_votes) >= required: await ctx.send( embed=embed(title='Vote to stop passed. Stopping the player.', color=MessageColors.MUSIC)) await player.teardown() else: await ctx.send(embed=embed( title=f'{ctx.author} has voted to stop the player.', color=MessageColors.MUSIC))
async def vote_remind(self, ctx: "MyContext"): current_reminder = bool(await self.bot.db.query("SELECT to_remind FROM votes WHERE id=$1 LIMIT 1", str(ctx.author.id))) await self.bot.db.query("INSERT INTO votes (id,to_remind) VALUES ($1,$2) ON CONFLICT(id) DO UPDATE SET to_remind=$3", str(ctx.author.id), not current_reminder, not current_reminder) if current_reminder is not True: await ctx.send(embed=embed(title="I will now DM you every 12 hours after you vote for when you can vote again")) elif current_reminder is True: await ctx.send(embed=embed(title="I will stop DMing you for voting reminders 😢"))
async def max_content_spam(self, ctx: "MyContext", message_rate: int, seconds: int): if message_rate < 3 or seconds < 5: return await ctx.send(embed=embed( title="Some arguments are too small", description= "`message_rate` must be greater than 3\n`seconds` must be greater than 5", color=MessageColors.ERROR)) current = await self.bot.db.query( "SELECT max_content FROM servers WHERE id=$1 LIMIT 1", str(ctx.guild.id)) if current is None or current == "null": current = {"punishments": ["mute"]} else: current = json.loads(current) punishments = current["punishments"] if "punishments" in current else [ "mute" ] value = json.dumps({ "rate": message_rate, "seconds": seconds, "punishments": punishments }) await self.bot.db.query( "UPDATE servers SET max_content=$1 WHERE id=$2", value, str(ctx.guild.id)) await ctx.reply(embed=embed( title= f"I will now delete messages matching the same content that are sent more than the rate of `{message_rate}` message, for every `{seconds}` seconds." ))
async def rock_paper_scissors(self, ctx: "MyContext", args: str) -> None: arg = args.lower() if arg not in self.rpsoptions: return await ctx.send(embed=embed( title= f"`{arg}` is not Rock, Paper, Scissors. Please choose one of those three.", color=MessageColors.ERROR)) mychoice = random.choice(self.rpsoptions) if mychoice == arg: conclusion = "Draw" elif mychoice == "rock" and arg == "paper": conclusion = self.bot.user elif mychoice == "rock" and arg == "scissors": conclusion = self.bot.user elif mychoice == "paper" and arg == "scissors": conclusion = ctx.author elif mychoice == "paper" and arg == "rock": conclusion = ctx.author elif mychoice == "scissors" and arg == "rock": conclusion = ctx.author elif mychoice == "scissors" and arg == "paper": conclusion = self.bot.user return await ctx.send(embed=embed( title=f"Your move: {arg} VS My move: {mychoice}", color=MessageColors.RPS, description=f"The winner of this round is: **{conclusion}**"))
async def max_mentions(self, ctx: "MyContext", mention_count: int, seconds: int): if mention_count < 3 and seconds < 5: return await ctx.send( embed=embed(title="Count must be greater than 3", color=MessageColors.ERROR)) current = await self.bot.db.query( "SELECT max_mentions FROM servers WHERE id=$1 LIMIT 1", str(ctx.guild.id)) if current is None or current == "null": current = {"punishments": ["mute"]} else: current = json.loads(current) punishments = current["punishments"] await self.bot.db.query( "UPDATE servers SET max_mentions=$1 WHERE id=$2", json.dumps({ "mentions": mention_count, "seconds": seconds, "punishments": punishments }), str(ctx.guild.id)) await ctx.reply(embed=embed( title= f"I will now apply the punishments `{', '.join(punishments)}` to members that mention `>={mention_count}` within `{seconds}` seconds." ))
async def custom(self, ctx, name: str = None): if name is None: return await ctx.invoke(self.custom_list) try: async with ctx.typing(): sounds = await self.bot.db.query( "SELECT customSounds FROM servers WHERE id=$1 LIMIT 1", str(ctx.guild.id)) sounds = [json.loads(x) for x in sounds] except Exception: await ctx.reply(embed=embed( title= f"The custom sound `{name}` has not been set, please add it with `{ctx.prefix}custom|c add <name> <url>`", color=MessageColors.ERROR)) else: i = next( (index for (index, d) in enumerate(sounds) if d["name"] == name), None) if sounds is not None and i is not None: sound = sounds[i] await ctx.invoke(self.bot.get_command("play"), query=sound["url"]) else: await ctx.reply(embed=embed( title= f"The sound `{name}` has not been added, please check the `custom list` command", color=MessageColors.ERROR))
async def custom_add(self, ctx, name: str, url: str): url = url.strip("<>") valid = validators.url(url) if valid is not True: await ctx.reply( embed=embed(title=f"Failed to recognize the url `{url}`", color=MessageColors.ERROR)) return if name in ["add", "change", "replace", "list", "remove", "del"]: await ctx.reply(embed=embed( title= f"`{name}`is not an acceptable name for a command as it is a sub-command of custom", color=MessageColors.ERROR)) return async with ctx.typing(): name: str = "".join(name.split(" ")).lower() sounds: list = (await self.bot.db.query( "SELECT customSounds FROM servers WHERE id=$1 LIMIT 1", str(ctx.guild.id))) if sounds == "" or sounds is None: sounds = [] if name in [json.loads(x)["name"] for x in sounds]: return await ctx.reply(embed=embed( title=f"`{name}` was already added, please choose another", color=MessageColors.ERROR)) sounds.append(json.dumps({"name": name, "url": url})) await self.bot.db.query( "UPDATE servers SET customSounds=$1::json[] WHERE id=$2::text", sounds, str(ctx.guild.id)) await ctx.reply(embed=embed( title= f"I will now play `{url}` for the command `{ctx.prefix}{ctx.command.parent} {name}`" ))
async def norm_unmute(self, ctx: "MyContext", members: commands.Greedy[discord.Member], *, reason: ActionReason = None): if not isinstance(members, list): members = [members] if reason is None: reason = f"[Unmuted by {ctx.author} (ID: {ctx.author.id})]" role = discord.Object(id=ctx.guild_config.mute_role_id) if len(members) == 0: return await ctx.send(embed=embed( title="Missing members to unmute.", color=MessageColors.ERROR)) failed = 0 async with ctx.typing(): for member in members: try: await member.remove_roles(role, reason=reason) except discord.HTTPException: failed += 1 await ctx.send(embed=embed( title=f"Unmuted {len(members) - failed}/{len(members)} members"))
async def resume(self, ctx: MyContext): """Resume a currently paused player.""" player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, ctx=ctx) if not player.is_paused or not player.is_connected: return await ctx.send( embed=embed(title='Nothing is playing right now', color=MessageColors.ERROR)) if self.is_privileged(ctx): await ctx.send( embed=embed(title='An admin or DJ has resumed the player.', color=MessageColors.MUSIC)) player.resume_votes.clear() return await player.set_pause(False) required = self.required(ctx) player.resume_votes.add(ctx.author) if len(player.resume_votes) >= required: await ctx.send( embed=embed(title='Vote to resume passed. Resuming player.', color=MessageColors.MUSIC)) player.resume_votes.clear() await player.set_pause(False) else: await ctx.send(embed=embed( title=f'{ctx.author.mention} has voted to resume the player.', color=MessageColors.MUSIC))
async def last_member( self, ctx: "MyContext", *, voice_channel: Optional[discord.VoiceChannel] = None): if voice_channel is None and ctx.author.voice is None: return await ctx.reply(embed=embed( title="You must either select a voice channel or be in one.", color=MessageColors.ERROR)) if voice_channel is None: voice_channel = ctx.author.voice.channel if not isinstance(voice_channel, discord.VoiceChannel): return await ctx.reply( embed=embed(title="That is not a voice channel.", color=MessageColors.ERROR)) member = self.last_to_leave_vc[voice_channel.id] if member is None: return await ctx.reply(embed=embed( title= f"No currently saved departing member of `{voice_channel}` saved.", description="I'll catch the next one :)", color=MessageColors.ERROR)) await ctx.reply(embed=embed( title= f"`{member['member']}` left `{voice_channel}` <t:{int(member['time'])}:R>." ))
async def mute_role_create(self, ctx: "MyContext", *, name: Optional[str] = "Muted"): config = await self.get_guild_config(ctx.guild.id) if config.mute_role is not None: ctx.command.reset_cooldown(ctx) return await ctx.send( embed=embed(title="This server already has a mute role.", color=MessageColors.ERROR)) try: role = await ctx.guild.create_role( name=name, reason= f"Mute Role created by {ctx.author} (ID: {ctx.author.id})") except discord.HTTPException as e: ctx.command.reset_cooldown(ctx) return await ctx.send(embed=embed(title="An error occurred", description=str(e), color=MessageColors.ERROR)) await self.bot.db.query("UPDATE servers SET mute_role=$1 WHERE id=$2", str(role.id), str(ctx.guild.id)) confirm = await ctx.prompt( "Would you like to update the channel overwrites") if not confirm: return await ctx.send(embed=embed( title="Mute role successfully created.")) async with ctx.typing(): success, failed, skipped = 0, 0, 0 for channel in ctx.guild.channels: perms = channel.permissions_for(ctx.guild.me) if perms.manage_roles: try: await channel.set_permissions( role, send_messages=False, send_messages_in_threads=False, create_public_threads=False, create_private_threads=False, speak=False, add_reactions=False, reason= f"Mute role overwrites by {ctx.author} (ID: {ctx.author.id})" ) except discord.HTTPException: failed += 1 else: success += 1 else: skipped += 1 await ctx.send(embed=embed( title="Mute role successfully created.", description= f"Overwrites:\nUpdated: {success}, Failed: {failed}, Skipped: {skipped}" ))
async def _welcome_channel(self, ctx: "MyContext", channel: Optional[discord.TextChannel] = None): if channel is not None and channel.permissions_for(ctx.guild.me).send_messages is False: return await ctx.reply(embed=embed(title=f"I don't have send_permissions in {channel}", color=MessageColors.ERROR)) channel_id = channel.id if channel is not None else None await self.bot.db.query("INSERT INTO welcome (guild_id,channel_id) VALUES ($1,$2) ON CONFLICT(guild_id) DO UPDATE SET channel_id=$2", str(ctx.guild.id), str(channel_id) if channel_id else None) message = await self.bot.db.query("SELECT message FROM welcome WHERE guild_id=$1 LIMIT 1", str(ctx.guild.id)) self.get_guild_config.invalidate(self, ctx.guild.id) await ctx.reply(embed=embed(title=f"Welcome message will be sent to `{channel}`", description="" if message is not None else "Don't forget to set a welcome message"))
async def _welcome_display(self, ctx: "MyContext"): config = await self.get_guild_config(ctx.guild.id) if config is None: return await ctx.send(embed=embed(title="Welcome", description="Welcome is not enabled for this server.", color=MessageColors.ERROR)) await ctx.reply(embed=embed( title="Current Welcome Settings", fieldstitle=["Role", "Channel", "Message"], fieldsval=[f"<@&{config.role_id}>"if str(config.role_id) != str(None) else "None", f"<#{config.channel_id}>" if str(config.channel_id) != str(None) else "None", f"{config.message}" if config.message != "" else "None"], fieldsin=[False, False, False] ))
async def max_content_spam_error(self, ctx: "MyContext", error): if isinstance(error, commands.MissingRequiredArgument): config = await self.get_guild_config(ctx.guild.id) if config is None or config.max_content is None: return await ctx.send(embed=embed(title="No settings found", color=MessageColors.ERROR)) return await ctx.send(embed=embed( title="Current message content spam settings", description= f"Message count: `{config.max_content['rate']}`\nSeconds: `{config.max_content['seconds']}`\nPunishments: `{', '.join(config.max_content['punishments'])}`" ))
async def _blacklist_display_words(self, ctx): words = await self.bot.db.query( "SELECT words FROM blacklist WHERE guild_id=$1 LIMIT 1", str(ctx.guild.id)) if words == [] or words is None: return await ctx.reply(embed=embed( title= f"No blacklisted words yet, use `{ctx.prefix}blacklist add <word>` to get started" )) await ctx.reply(embed=embed(title="Blocked words", description='\n'.join(x for x in words)))
async def reset_history(self, ctx: "MyContext"): try: self.chat_history.pop(ctx.channel.id) except KeyError: await ctx.send(embed=embed(title="No history to delete")) except Exception as e: raise e else: await ctx.send(embed=embed( title="My chat history has been reset", description="I have forgotten the last few messages"))
async def _blacklist_add_word(self, ctx, *, phrase: str): if len(await self.bot.db.query( "SELECT words FROM blacklist WHERE guild_id=$1::text AND $2::text = ANY(words)", str(ctx.guild.id), phrase)) > 0: return await ctx.reply(embed=embed( title="Can't add duplicate word", color=MessageColors.ERROR)) await self.bot.db.query( "INSERT INTO blacklist (guild_id,words) VALUES ($1::text,array[$2]::text[]) ON CONFLICT(guild_id) DO UPDATE SET words = array_append(blacklist.words, $2)", str(ctx.guild.id), phrase) phrase = phrase await ctx.reply(embed=embed(title=f"Added `{phrase}` to the blacklist") )
async def mute_role(self, ctx: "MyContext", *, role: Optional[discord.Role] = None): await self.bot.db.query("UPDATE servers SET mute_role=$1 WHERE id=$2", str(role.id) if role is not None else None, str(ctx.guild.id)) if role is not None: return await ctx.send(embed=embed( title=f"Friday will now use `{role}` as the new mute role")) await ctx.send(embed=embed( title="The saved mute role has been removed"))
async def _prefix(self, ctx: "MyContext", new_prefix: Optional[str] = config.defaultPrefix): new_prefix = new_prefix.lower() if len(new_prefix) > 5: return await ctx.reply(embed=embed( title="Can't set a prefix with more than 5 characters", color=MessageColors.ERROR)) await self.bot.db.query("UPDATE servers SET prefix=$1 WHERE id=$2", str(new_prefix), str(ctx.guild.id)) self.bot.prefixes[ctx.guild.id] = new_prefix await ctx.reply(embed=embed(title=f"My new prefix is `{new_prefix}`"))
async def reload_all(self, ctx): async with ctx.typing(): if self.bot.canary: stdout, stderr = await self.run_process( "git pull origin canary && git submodule update") elif self.bot.prod: stdout, stderr = await self.run_process( "git pull origin master && git submodule update") else: return await ctx.reply(embed=embed( title="You are not on a branch", color=MessageColors.ERROR) ) if stdout.startswith("Already up-to-date."): return await ctx.send(stdout) modules = self.modules_from_git(stdout) mods_text = "\n".join( f"{index}. `{module}`" for index, (_, module) in enumerate(modules, start=1)) confirm = await ctx.prompt( f"This will update the following modules, are you sure?\n{mods_text}" ) if not confirm: return await ctx.send("Aborting.") statuses = [] for is_func, module in modules: if is_func: try: actual_module = sys.modules[module] except KeyError: statuses.append((":zzz:", module)) else: try: importlib.reload(actual_module) except Exception: statuses.append((":x:", module)) else: statuses.append((":white_check_mark:", module)) else: try: self.reload_or_load_extention(module) except discord.ExtensionError: statuses.append((":x:", module)) else: statuses.append((":white_check_mark:", module)) await ctx.send(embed=embed(title="Reloading modules", description="\n".join( f"{status} {module}" for status, module in statuses)))
async def extract_toggle(self, ctx, enable: bool): await self.bot.db.query( "UPDATE servers SET reddit_extract=$1 WHERE id=$2", enable, str(ctx.guild.id)) if enable: return await ctx.send(embed=embed( title="I will now react to Reddit links", description= "For me to then extract a reddit link the author of the message must react with the same emoji Friday did.\nFriday also requires add_reaction permissions (if not already) for this to work." )) await ctx.send(embed=embed( title="I will no longer react to Reddit links.", description="The Reddit extract commands will still work."))
async def _blacklist_punishment(self, ctx, *, action: str): action = [ i for i in action.split(" ") if i.lower() in PUNISHMENT_TYPES ] if len(action) == 0: return await ctx.send(embed=embed( title= f"The action must be one of the following: {', '.join(PUNISHMENT_TYPES)}", color=MessageColors.ERROR)) await self.bot.db.query( "UPDATE blacklist SET punishments=$1 WHERE guild_id=$2", action, str(ctx.guild.id)) await ctx.reply(embed=embed( title=f"Set punishment to `{', '.join(action)}`"))
async def _welcome_message(self, ctx: "MyContext", *, message: Optional[str] = None): if message is not None and len(message) > 255: await ctx.reply(embed=embed(title="Welcome messages can't be longer than 255 characters", color=MessageColors.ERROR)) await self.bot.db.query("INSERT INTO welcome (guild_id,message) VALUES ($1,$2) ON CONFLICT(guild_id) DO UPDATE SET message=$2", str(ctx.guild.id), message) formated_message, message_variables = message, [r"{user}", r"{server}"] if message is not None and any(var in message.lower() for var in message_variables): for var in message_variables: if var == r"{user}": formated_message = f"@{ctx.author.name}".join(formated_message.split(var)) elif var == r"{server}": formated_message = f"{ctx.guild.name}".join(formated_message.split(var)) channel_id = await self.bot.db.query("SELECT channel_id FROM welcome WHERE guild_id=$1 LIMIT 1", str(ctx.guild.id)) self.get_guild_config.invalidate(self, ctx.guild.id) await ctx.reply(embed=embed(title="This servers welcome message is now", description=f"```{message}```\n\nThis will look like\n```{formated_message}```" + ("" if channel_id is not None else "\n\n**Don't forget to set a welcome channel**")))
async def dice(self, ctx: "MyContext", roll): roll = roll.lower() result = None try: result = d20.roll(roll) except Exception as e: return await ctx.send( embed=embed(title=f"{e}", color=MessageColors.ERROR)) else: return await ctx.send(embed=embed( title=f"Your total: {str(result.total)}", description=f"Query: {str(result.ast)}\nResult: {str(result)}") )
async def _blacklist_remove_word(self, ctx, *, word: str): cleansed_word = word current_words = await self.bot.db.query( "SELECT words FROM blacklist WHERE guild_id=$1 AND $2::text = ANY(words) LIMIT 1", str(ctx.guild.id), cleansed_word) if current_words is None or len(current_words) == 0: return await ctx.reply(embed=embed( title="You don't seem to be blacklisting that word")) await self.bot.db.query( "UPDATE blacklist SET words = array_remove(words,$2::text) WHERE guild_id=$1", str(ctx.guild.id), cleansed_word) word = word await ctx.reply(embed=embed( title=f"Removed `{word}` from the blacklist"))
async def unban(self, ctx, member: BannedMember, *, reason: ActionReason = None): if reason is None: reason = f"[Unbanned by {ctx.author} (ID: {ctx.author.id})]" await ctx.guild.unban(member.user, reason=reason) if member.reason: return await ctx.send(embed=embed( title=f"Unbanned {member.user} (ID: {member.user.id})", description=f"Previously banned for `{member.reason}`.")) await ctx.send(embed=embed( title=f"Unbanned {member.user} (ID: {member.user.id})."))
async def connect(self, ctx: MyContext, *, channel: Optional[Union[discord.VoiceChannel, discord.StageChannel]] = None): """Connect to a voice channel.""" ch = await self.join(ctx, channel) if ch: return await ctx.send(f"{ch.mention}", embed=embed( title="Connected to voice channel", color=MessageColors.MUSIC)) return await ctx.send( embed=embed(title="Failed to connect to voice channel.", color=MessageColors.ERROR))
async def on_dbl_vote(self, data, time=datetime.datetime.now()): self.bot.logger.info(f'Received an upvote, {data}') if data.get("type", None) == "test": time = datetime.datetime.now() - datetime.timedelta(hours=11, minutes=59) if data.get("user", None) is not None: await self.bot.db.query("INSERT INTO votes (id,voted_time) VALUES ($1,$2) ON CONFLICT(id) DO UPDATE SET has_reminded=false,voted_time=$2", str(data["user"]), time) if data.get("type", None) == "test" or int(data.get("user", None), base=10) not in (215227961048170496, 813618591878086707): if data.get("user", None) is not None: support_server = self.bot.get_guild(config.support_server_id) member = await self.bot.get_or_fetch_member(support_server, data["user"]) if member is not None: try: await member.add_roles(discord.Object(id=VOTE_ROLE), reason="Voted on Top.gg") except discord.HTTPException: pass else: self.bot.logger.info(f"Added vote role to {member.id}") await self.log_bumps.send( username=self.bot.user.name, avatar_url=self.bot.user.display_avatar.url, embed=embed( title=f"Somebody Voted - {data.get('type',None)}", fieldstitle=["Member", "Is week end"], fieldsval=[ f'{self.bot.get_user(data.get("user",None))} (ID: {data.get("user",None)})', f'{data.get("isWeekend",None)}'], fieldsin=[False, False] ) )