async def _confirm(ctx: commands.Context) -> bool: """Ask "Are you sure?" and get the response as a bool.""" if ctx.guild is None or ctx.guild.me.permissions_in(ctx.channel).add_reactions: msg = await ctx.send(_("Are you sure?")) # noinspection PyAsyncCall task = start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS, ctx.bot.loop) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=30) except asyncio.TimeoutError: await ctx.send(_("Response timed out.")) return False else: task.cancel() agreed = pred.result finally: await msg.delete() else: await ctx.send(_("Are you sure? (y/n)")) pred = MessagePredicate.yes_or_no(ctx) try: await ctx.bot.wait_for("message", check=pred, timeout=30) except asyncio.TimeoutError: await ctx.send(_("Response timed out.")) return False else: agreed = pred.result if agreed is False: await ctx.send(_("Action cancelled.")) return agreed
async def cast(self, ctx:commands.Context, bait_type:str): """Rolls for a fish Fish will periodically bite the pole, at which point the message can be reacted to to catch the fish After reeling in the rod, you will have the option to keep or release the fish - Must be used in a channel registered as a pool""" if not await self.IsSpecialized(ctx.guild, ctx.channel.id, POOL_CHANNEL): return profile = self.config.member(ctx.message.author) await profile.currently_fishing.set(True) modified_fish_weights = await self.startfishing(ctx, profile, bait_type) embed = Embed(title=f'{ctx.message.author.display_name} cast their rod into the shimmering waves at {ctx.channel}', color=0x7300ff) embed.set_footer(text='Not even a nibble yet...') msg = await ctx.send(embed=embed) start_adding_reactions(msg, ['🎣']) pred = ReactionPredicate.with_emojis(['🎣'], msg, ctx.author) time_left = await self.GetSetting(ctx.guild, 'max_fishing_length') min_pause = await self.GetSetting(ctx.guild, 'min_fishing_wait') max_pause = await self.GetSetting(ctx.guild, 'max_fishing_wait') curr_fish = None rarity = None while time_left >= 0: try: timer = time_left if time_left < max_pause else randint(min_pause, max_pause) time_left -= timer await ctx.bot.wait_for('reaction_add', check=pred, timeout=timer) except asyncio.TimeoutError: if curr_fish is None: rarity = choices(FISH_RARITIES, modified_fish_weights)[0] rarity_list = self.fishing_rarities.get(rarity) curr_fish = rarity_list[randint(0, len(rarity_list) - 1)] if not await profile.bryan_mode() else self.SEA_BASS embed.set_footer(text=RARITY_DESCRIPTIONS[rarity]) else: curr_fish = None embed.set_footer(text='The rod drifts in the water') await msg.edit(embed=embed) if pred.result == 0: break if curr_fish is None or time_left <= 0: embed.set_footer(text='You feel a twist as the line snaps :(') await msg.edit(embed=embed) await msg.clear_reactions() else: new_fish = curr_fish.ToFishCatch(RARITY_VALUES[rarity]) embed.set_footer(text=f'You pulled a {new_fish["name"]} ({new_fish["size"]} inches) out of the water!\nDo you want to keep or release?') embed.set_thumbnail(url=curr_fish.image) await msg.edit(embed=embed) await msg.clear_reactions() start_adding_reactions(msg, ['🥤', '🐟']) pred = ReactionPredicate.with_emojis(['🥤', '🐟'], msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=15) except asyncio.TimeoutError: if await self.AddFish(ctx.message.author, new_fish): embed.set_footer(text=f'Timed out, {new_fish["name"]} was added to your bucket') else: embed.set_footer(text=f'Timed out and your bucket was full, so {new_fish["name"]} was released :(') await msg.edit(embed=embed) await msg.clear_reactions() else: if pred.result == 0: if await self.AddFish(ctx.message.author, new_fish): embed.set_footer(text=f'{new_fish["name"]} was added to your bucket!') else: embed.set_footer(text=f'Your bucket was full, so you had to release {new_fish["name"]} :(') else: embed.set_footer(text=f'You let {new_fish["name"]} swim away...') await msg.edit(embed=embed) await msg.clear_reactions() if randint(0, 100) < 100 * await self.GetSetting(ctx.guild, 'bait_recovery_chance'): await ctx.send(f'Your {bait_type} is still on the end of the rod! (+1 {bait_type})') else: user_bait = await profile.bait() user_bait[bait_type] -= 1 await profile.bait.set(user_bait) await profile.currently_fishing.set(False) #if not await profile.mawiam_mode(): #await profile.nextcast.set(time() + await self.GetSetting(ctx.guild, 'fishing_delay')) await self.CheckSchools(ctx)
async def test_radar_minigame(self, ctx): """Mini-jeu où on doit faire passer un satellite d'un côté à l'autre Intégration prévue dans le prochain jeu 'Astral'""" user, guild = ctx.author, ctx.guild arrows = ['➡️', '⬅️', '⬆️', '⬇️', '↗️', '↘️', '↙️', '↖️'] goods = random.sample(arrows, k=3) affs = [ f"🛰️ {goods[0]} · {goods[1]} · {goods[2]} ✅", f"· {goods[0]} 🛰️ {goods[1]} · {goods[2]} ✅", f"· {goods[0]} · {goods[1]} 🛰️ {goods[2]} ✅", f"· {goods[0]} · {goods[1]} · {goods[2]} 🛰️" ] affnb = 0 random.shuffle(arrows) msg = None while affnb < 3: timeout = 5 if affnb > 0 else 7 em = discord.Embed(description=box(affs[affnb]), color=user.color) em.set_footer( text= f"› Cliquez sur les bonnes réactions dans l'ordre ({timeout}s)" ) if not msg: msg = await ctx.send(embed=em) start_adding_reactions(msg, arrows) else: await msg.edit(embed=em) try: react, _ = await self.bot.wait_for( "reaction_add", check=lambda m, u: u == ctx.author and m.message.id == msg. id, timeout=5) except asyncio.TimeoutError: em.description = box(affs[affnb].replace('🛰️', '💥')) txt = ["Loupé", "Manqué", "Echec"] nrg = random.randint(4, 8) em.set_footer( text=f"{random.choice(txt)} › Vous perdez {nrg}x ⚡") try: await msg.clear_reactions() except: pass return await msg.edit(embed=em) if react.emoji == goods[affnb]: affnb += 1 continue else: em.description = box(affs[affnb].replace('🛰️', '💥')) txt = ["Loupé", "Manqué", "Echec"] nrg = random.randint(2, 5) em.set_footer( text=f"{random.choice(txt)} › Vous perdez {nrg}x ⚡") try: await msg.clear_reactions() except: pass return await msg.edit(embed=em) em = discord.Embed(description=box(affs[affnb]), color=user.color) em.set_footer(text="Vous avez réussi !") try: await msg.clear_reactions() except: pass await msg.edit(embed=em)
async def make_event( self, ctx: commands.Context, members: commands.Greedy[discord.Member], max_slots: Optional[int] = None, *, description: str, ) -> None: """ Create an event `[members...]` Add members already in the event you want to host. `[max_slots=None]` Specify maximum number of Slots the event can have, default is no limit. `<description>` provide a description for the event you're hosting. With custom keyword links setup this will add an image to the events thumbnail after being approved by an admin. """ approval_channel = ctx.guild.get_channel(await self.config.guild( ctx.guild).approval_channel()) announcement_channel = ctx.guild.get_channel(await self.config.guild( ctx.guild).announcement_channel()) if not approval_channel: return await ctx.send( "No admin channel has been setup on this server. Use `[p]eventset approvalchannel` to add one." ) if not announcement_channel: return await ctx.send( "No announcement channel has been setup on this server. Use `[p]eventset channel` to add one." ) if str(ctx.author.id) in await self.config.guild(ctx.guild).events(): if not await self.check_clear_event(ctx): return if ctx.author not in members: members.insert(0, ctx.author) member_list = [] for member in members: member_list.append((member, await self.config.member(member).player_class())) if not max_slots: max_slots = await self.config.guild(ctx.guild).default_max() # log.debug(f"using default {max_slots}") event = Event(hoster=ctx.author, members=list(member_list), event=description, max_slots=max_slots) em = await self.make_event_embed(ctx, event) admin_msg = await approval_channel.send(embed=em) start_adding_reactions(admin_msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(admin_msg) reaction, user = await ctx.bot.wait_for("reaction_add", check=pred) if pred.result: ping = await self.config.guild(ctx.guild).ping() publish = (await self.config.guild(ctx.guild).publish() and announcement_channel.is_news()) event.approver = user event.channel = announcement_channel em.set_footer(text=f"Approved by {user}", icon_url=user.avatar_url) posted_message = await announcement_channel.send(ping, embed=em, **self.sanitize) if publish: try: await posted_message.publish() except (discord.errors.Forbidden, discord.errors.HTTPException): log.debug("Event Channel is not a news channel.") pass event.message = posted_message async with self.config.guild(ctx.guild).events() as cur_events: cur_events[str(event.hoster.id)] = event.to_json() if ctx.guild.id not in self.event_cache: self.event_cache[ctx.guild.id] = {} self.event_cache[ctx.guild.id][posted_message.id] = event try: start_adding_reactions(posted_message, EVENT_EMOJIS) except discord.errors.Forbidden: pass else: await ctx.send( f"{ctx.author.mention}, your event request was denied by an admin." ) await admin_msg.delete() return
async def convert(self, ctx: commands.Context, argument: str) -> Union[List[str], List[int]]: result = [] match = re.split(r"(;)", argument) valid_reactions = [ "dm", "dmme", "remove_role", "add_role", "ban", "kick", "text", "filter", "delete", "publish", "react", "rename", "command", "mock", ] log.debug(match) my_perms = ctx.channel.permissions_for(ctx.me) if match[0] not in valid_reactions: raise BadArgument( _("`{response}` is not a valid reaction type.").format( response=match[0])) for m in match: if m == ";": continue else: result.append(m) if result[0] == "filter": result[0] = "delete" if len(result) < 2 and result[0] not in ["delete", "ban", "kick"]: raise BadArgument( _("The provided multi response pattern is not valid.")) if result[0] in ["add_role", "remove_role" ] and not my_perms.manage_roles: raise BadArgument( _('I require "Manage Roles" permission to use that.')) if result[0] == "filter" and not my_perms.manage_messages: raise BadArgument( _('I require "Manage Messages" permission to use that.')) if result[0] == "publish" and not my_perms.manage_messages: raise BadArgument( _('I require "Manage Messages" permission to use that.')) if result[0] == "ban" and not my_perms.ban_members: raise BadArgument( _('I require "Ban Members" permission to use that.')) if result[0] == "kick" and not my_perms.kick_members: raise BadArgument( _('I require "Kick Members" permission to use that.')) if result[0] == "react" and not my_perms.add_reactions: raise BadArgument( _('I require "Add Reactions" permission to use that.')) if result[0] == "mock": msg = await ctx.send( _("Mock commands can allow any user to run a command " "as if you did, are you sure you want to add this?")) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=15) except asyncio.TimeoutError: raise BadArgument(_("Not creating trigger.")) if not pred.result: raise BadArgument(_("Not creating trigger.")) def author_perms(ctx: commands.Context, role: discord.Role) -> bool: if ( ctx.author.id == ctx.guild.owner_id ): # handles case where guild is not chunked and calls for the ID thru the endpoint instead return True return role < ctx.author.top_role if result[0] in ["add_role", "remove_role"]: good_roles = [] for r in result[1:]: try: role = await RoleConverter().convert(ctx, r) if role < ctx.guild.me.top_role and author_perms( ctx, role): good_roles.append(role.id) except BadArgument: log.error("Role `{}` not found.".format(r)) result = [result[0]] for r_id in good_roles: result.append(r_id) if result[0] == "react": good_emojis: List[Union[discord.Emoji, str]] = [] for r in result[1:]: try: emoji = await ValidEmoji().convert(ctx, r) good_emojis.append(emoji) except BadArgument: log.error("Emoji `{}` not found.".format(r)) log.debug(good_emojis) result = [result[0]] + good_emojis return result
async def bulkreact( self, ctx: Context, message: discord.Message, *role_emoji: RoleEmojiConverter, ): """ Create multiple roles reactions for a single message `<message>` can be the channel_id-message_id pair from copying message ID while holding SHIFT or a message link `[role_emoji...]` Must be a role-emoji pair separated by either `;`, `,`, `|`, or `-`. Note: Any spaces will be considered a new set of role-emoji pairs so ensure there's no spaces between the role-emoji pair. e.g. `[p]roletools bulkreact 461417772115558410-821105109097644052 @member-:smile:` `[p]roletools bulkreact 461417772115558410-821105109097644052 role-:frown:` """ if not message.guild or message.guild.id != ctx.guild.id: return await ctx.send( _("You cannot add a Reaction Role to a message not in this guild.") ) added = [] not_added = [] send_to_react = False async with self.config.guild(ctx.guild).reaction_roles() as cur_setting: for role, emoji in role_emoji: log.debug(type(emoji)) if isinstance(emoji, discord.PartialEmoji): use_emoji = str(emoji.id) else: use_emoji = str(emoji).strip("\N{VARIATION SELECTOR-16}") key = f"{message.channel.id}-{message.id}-{use_emoji}" if key not in cur_setting: try: await message.add_reaction( str(emoji).strip().strip("\N{VARIATION SELECTOR-16}") ) except discord.HTTPException: send_to_react = True log.exception("could not add reaction to message") pass if ctx.guild.id not in self.settings: self.settings[ctx.guild.id] = await self.config.guild(ctx.guild).all() self.settings[ctx.guild.id]["reaction_roles"][key] = role.id cur_setting[key] = role.id added.append((key, role)) async with self.config.role(role).reactions() as reactions: reactions.append(key) else: not_added.append((key, role)) ask_to_modify = False if added: msg = _("__The following Reaction Roles were created__\n") if any( [ m is False for m in [await self.config.role(r).selfassignable() for x, r in added] ] ): ask_to_modify = True for item, role in added: channel, message_id, emoji = item.split("-") if emoji.isdigit(): emoji = self.bot.get_emoji(int(emoji)) msg += _("{role} - {emoji} on {message}\n").format( role=role.name, emoji=emoji, message=message.jump_url ) for page in pagify(msg): await ctx.send(page) if send_to_react: await ctx.send( _( "I couldn't add an emoji to the message. Please make " "sure to add the missing emojis to the message for this to work." ) ) if not_added: msg = _("__The following Reaction Roles could not be created__\n") for item, role in not_added: channel, message_id, emoji = item.split("-") if emoji.isdigit(): emoji = self.bot.get_emoji(int(emoji)) msg += _("{role} - {emoji} on {message}\n").format( role=role.name, emoji=emoji, message=message.jump_url ) await ctx.send(msg) if ask_to_modify: msg_str = _( "Some roles are not self assignable. Would you liked to make " "them self assignable and self removeable?" ).format(role=role.name, prefix=ctx.clean_prefix) msg = await ctx.send(msg_str) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=60) except asyncio.TimeoutError: return await ctx.send( _("Okay I won't automatically make {role} self assignable.").format( role=role.name ) ) if pred.result: for key, role in added: await self.config.role(role).selfassignable.set(True) await self.config.role(role).selfremovable.set(True) await ctx.send( _("{roles} have been made self assignable and self removeable.").format( roles=humanize_list([r for x, r in added]) ) )
async def command_now(self, ctx: commands.Context): """Now playing.""" if not self._player_check(ctx): return await self.send_embed_msg(ctx, title=_("Nothing playing.")) emoji = { "prev": "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}", "stop": "\N{BLACK SQUARE FOR STOP}\N{VARIATION SELECTOR-16}", "pause": "\N{BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR}\N{VARIATION SELECTOR-16}", "next": "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}", "close": "\N{CROSS MARK}", } expected = tuple(emoji.values()) player = lavalink.get_player(ctx.guild.id) if player.current: arrow = await self.draw_time(ctx) pos = self.format_time(player.position) if player.current.is_stream: dur = "LIVE" else: dur = self.format_time(player.current.length) song = (await self.get_track_description( player.current, self.local_folder_current_path) or "") song += _("\n Requested by: **{track.requester}**").format( track=player.current) song += "\n\n{arrow}`{pos}`/`{dur}`".format(arrow=arrow, pos=pos, dur=dur) else: song = _("Nothing.") if player.fetch("np_message") is not None: with contextlib.suppress(discord.HTTPException): await player.fetch("np_message").delete() embed = discord.Embed(title=_("Now Playing"), description=song) guild_data = await self.config.guild(ctx.guild).all() if guild_data[ "thumbnail"] and player.current and player.current.thumbnail: embed.set_thumbnail(url=player.current.thumbnail) shuffle = guild_data["shuffle"] repeat = guild_data["repeat"] autoplay = guild_data["auto_play"] text = "" text += ( _("Auto-Play") + ": " + ("\N{WHITE HEAVY CHECK MARK}" if autoplay else "\N{CROSS MARK}")) text += ( (" | " if text else "") + _("Shuffle") + ": " + ("\N{WHITE HEAVY CHECK MARK}" if shuffle else "\N{CROSS MARK}")) text += ( (" | " if text else "") + _("Repeat") + ": " + ("\N{WHITE HEAVY CHECK MARK}" if repeat else "\N{CROSS MARK}")) message = await self.send_embed_msg(ctx, embed=embed, footer=text) player.store("np_message", message) dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()) vote_enabled = await self.config.guild(ctx.guild).vote_enabled() if ((dj_enabled or vote_enabled) and not await self._can_instaskip(ctx, ctx.author) and not await self.is_requester_alone(ctx)): return if not player.queue and not autoplay: expected = (emoji["stop"], emoji["pause"], emoji["close"]) task: Optional[asyncio.Task] if player.current: task = start_adding_reactions(message, expected[:5]) else: task = None try: (r, u) = await self.bot.wait_for( "reaction_add", check=ReactionPredicate.with_emojis(expected, message, ctx.author), timeout=30.0, ) except asyncio.TimeoutError: return await self._clear_react(message, emoji) else: if task is not None: task.cancel() reacts = {v: k for k, v in emoji.items()} react = reacts[r.emoji] if react == "prev": await self._clear_react(message, emoji) await ctx.invoke(self.command_prev) elif react == "stop": await self._clear_react(message, emoji) await ctx.invoke(self.command_stop) elif react == "pause": await self._clear_react(message, emoji) await ctx.invoke(self.command_pause) elif react == "next": await self._clear_react(message, emoji) await ctx.invoke(self.command_skip) elif react == "close": await message.delete()
async def inat_test(self, ctx): """Test command.""" msg = await ctx.send( embed=make_embed(title="Test", description="Reactions test.") ) start_adding_reactions(msg, ["\N{THREE BUTTON MOUSE}"])
async def setsuggest_setup(self, ctx: commands.Context): """ Go through the initial setup process. """ await self.config.guild(ctx.guild).same.set(False) await self.config.guild(ctx.guild).suggest_id.set(None) await self.config.guild(ctx.guild).approve_id.set(None) await self.config.guild(ctx.guild).reject_id.set(None) predchan = MessagePredicate.valid_text_channel(ctx) overwrites = { ctx.guild.default_role: discord.PermissionOverwrite(send_messages=False), ctx.guild.me: discord.PermissionOverwrite(send_messages=True), } msg = await ctx.send("Do you already have your channel(s) done?") start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await self.bot.wait_for("reaction_add", timeout=30, check=pred) except asyncio.TimeoutError: await msg.delete() return await ctx.send("You took too long. Try again, please.") if not pred.result: await msg.delete() suggestions = get(ctx.guild.text_channels, name="suggestions") if not suggestions: suggestions = await ctx.guild.create_text_channel( "suggestions", overwrites=overwrites, reason="Suggestion cog setup") await self.config.guild(ctx.guild).suggest_id.set(suggestions.id) msg = await ctx.send( "Do you want to use the same channel for approved and rejected suggestions? (If yes, they won't be reposted anywhere, only their title will change accordingly.)" ) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await self.bot.wait_for("reaction_add", timeout=30, check=pred) except asyncio.TimeoutError: await msg.delete() return await ctx.send("You took too long. Try again, please.") if pred.result: await msg.delete() await self.config.guild(ctx.guild).same.set(True) else: await msg.delete() approved = get(ctx.guild.text_channels, name="approved-suggestions") if not approved: msg = await ctx.send( "Do you want to have an approved suggestions channel?") start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await self.bot.wait_for("reaction_add", timeout=30, check=pred) except asyncio.TimeoutError: await msg.delete() return await ctx.send( "You took too long. Try again, please.") if pred.result: approved = await ctx.guild.create_text_channel( "approved-suggestions", overwrites=overwrites, reason="Suggestion cog setup", ) await self.config.guild(ctx.guild ).approve_id.set(approved.id) await msg.delete() else: await self.config.guild(ctx.guild ).approve_id.set(approved.id) rejected = get(ctx.guild.text_channels, name="rejected-suggestions") if not rejected: msg = await ctx.send( "Do you want to have a rejected suggestions channel?") start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await self.bot.wait_for("reaction_add", timeout=30, check=pred) except asyncio.TimeoutError: await msg.delete() return await ctx.send( "You took too long. Try again, please.") if pred.result: rejected = await ctx.guild.create_text_channel( "rejected-suggestions", overwrites=overwrites, reason="Suggestion cog setup", ) await self.config.guild(ctx.guild ).reject_id.set(rejected.id) await msg.delete() else: await self.config.guild(ctx.guild ).reject_id.set(rejected.id) else: await msg.delete() msg = await ctx.send( "Mention the channel where you want me to post new suggestions." ) try: await self.bot.wait_for("message", timeout=30, check=predchan) except asyncio.TimeoutError: await msg.delete() return await ctx.send("You took too long. Try again, please.") suggestion = predchan.result await self.config.guild(ctx.guild).suggest_id.set(suggestion.id) await msg.delete() msg = await ctx.send( "Do you want to use the same channel for approved and rejected suggestions? (If yes, they won't be reposted anywhere, only their title will change accordingly.)" ) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await self.bot.wait_for("reaction_add", timeout=30, check=pred) except asyncio.TimeoutError: await msg.delete() return await ctx.send("You took too long. Try again, please.") if pred.result: await msg.delete() await self.config.guild(ctx.guild).same.set(True) else: await msg.delete() msg = await ctx.send( "Do you want to have an approved suggestions channel?") start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await self.bot.wait_for("reaction_add", timeout=30, check=pred) except asyncio.TimeoutError: await msg.delete() return await ctx.send( "You took too long. Try again, please.") if pred.result: await msg.delete() msg = await ctx.send( "Mention the channel where you want me to post approved suggestions." ) try: await self.bot.wait_for("message", timeout=30, check=predchan) except asyncio.TimeoutError: await msg.delete() return await ctx.send( "You took too long. Try again, please.") approved = predchan.result await self.config.guild(ctx.guild ).approve_id.set(approved.id) await msg.delete() msg = await ctx.send( "Do you want to have a rejected suggestions channel?") start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await self.bot.wait_for("reaction_add", timeout=30, check=pred) except asyncio.TimeoutError: await msg.delete() return await ctx.send( "You took too long. Try again, please.") if pred.result: await msg.delete() msg = await ctx.send( "Mention the channel where you want me to post rejected suggestions." ) try: await self.bot.wait_for("message", timeout=30, check=predchan) except asyncio.TimeoutError: await msg.delete() return await ctx.send( "You took too long. Try again, please.") rejected = predchan.result await self.config.guild(ctx.guild ).reject_id.set(rejected.id) await msg.delete() await ctx.send( "You have finished the setup! Please, move your channels to the category you want them in." )
async def check_clear_event(self, ctx: commands.Context) -> bool: msg = await ctx.send("You already have an event running, would you like to cancel it?") start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) await ctx.bot.wait_for("reaction_add", check=pred) return pred.result
async def yamlscan(self, ctx: commands.Context): """Scan yaml to see if its correct. Your next message will be used to as the yaml to scan. You can also upload a YAML file. """ def cleanup_code(content) -> str: # From redbot.core.dev_commands, thanks will :P if content.startswith("```") and content.endswith("```"): return "\n".join(content.split("\n")[1:-1]) return content.strip("` \n") def tick(text) -> str: return "{} {}".format( "\N{BALLOT BOX WITH CHECK}\N{VARIATION SELECTOR-16}", text) def cross(text) -> str: return "{} {}".format("\N{CROSS MARK}", text) if ctx.message.attachments: # attachments will take priority file = ctx.message.attachments[0] if not file.filename.split(".")[-1] in ("yaml", "yml", "mir"): return await ctx.send("Please upload a valid YAML file.") try: file = await file.read() content = file.decode(encoding="utf-8") except UnicodeDecodeError: return await ctx.send( "Something went wrong whilst trying to decode the provided file." ) else: message = await ctx.send( "Your next message will be your YAML content:") check = lambda x: x.channel == ctx.channel and x.author == ctx.author try: content = await self.bot.wait_for("message", check=check, timeout=100) content = content.content except asyncio.TimeoutError: with contextlib.suppress(discord.NotFound): await message.edit("You took too long to respond.") return await ctx.send("You took too long to respond.") return try: yaml.full_load(cleanup_code(content)) except yaml.parser.MarkedYAMLError as e: message = cross( "This was **not** valid YAML. Would you like to see the exception details?" ) can_react = ctx.channel.permissions_for(ctx.me).add_reactions if not can_react: message += " (yes/no)" message = await ctx.send(message) if can_react: start_adding_reactions(message, ReactionPredicate.YES_OR_NO_EMOJIS) predicate = ReactionPredicate.yes_or_no(message, ctx.author) event_type = "reaction_add" else: predicate = MessagePredicate.yes_or_no(ctx) event_type = "message" try: await self.bot.wait_for(event_type, check=predicate, timeout=30) except asyncio.TimeoutError: with contextlib.suppress(discord.NotFound): await message.edit( content=cross("This was **not** valid YAML.")) if predicate.result: description = box("".join( traceback.format_exception(type(e), e, e.__traceback__)), lang="py") await message.clear_reactions() await message.edit(content=description) else: with contextlib.suppress(discord.NotFound): await message.edit( content=cross("This was **not** valid YAML.")) return await ctx.send(tick("This YAML looks good!"))
async def awbadge(self, ctx, tier: str = None, group: int = None): """Get alliance war badges.""" if group is not None and group >= 1 and group < 4: group_num = group - 1 # This is to make sure that it will work with the urls tiers = { "master": [ "https://media.discordapp.net/attachments/401476363707744257/738083791654092940/47EFB6D4D1380ABD2C40D2C7B0533A29245F7955.png", "https://media.discordapp.net/attachments/401476363707744257/738083791113027654/650E29ADB8C5C382FF5A358113B2C02B8EADA415.png", "https://media.discordapp.net/attachments/401476363707744257/738083791440052294/08BA0A081A9D56E35E60E3FD61FAB7ED9A10CD00.png" ], "platinum": [ "https://media.discordapp.net/attachments/401476363707744257/738083790718631937/E78E2BAF9B0C9BA6C7FE45BE726FFB0B0B0CACFD.png", "https://media.discordapp.net/attachments/401476363707744257/738083790362116116/487EA26A1BA0F2C2848E7C87F10430BD218C2178.png", "https://media.discordapp.net/attachments/401476363707744257/738083790559117352/0ED8BD10441C6D086AEB7BBA5271269F46E009D1.png" ], "gold": [ "https://media.discordapp.net/attachments/401476363707744257/738083790131298375/76BC21BF523A415866D19814BD8AF4BE16EF30A9.png", "https://media.discordapp.net/attachments/401476363707744257/738083998462509096/8CD52FEB7540016B6ABA1EC67B9F1777E3C29878.png", "https://media.discordapp.net/attachments/401476363707744257/738084001926873098/3A9A8FDA006D0BE225242AAA5909021CD52BCFB3.png" ], "silver": [ "https://media.discordapp.net/attachments/401476363707744257/738084001465499789/4B389D377A94EDA747B38DF640C0B33A3A3F61AE.png", "https://media.discordapp.net/attachments/401476363707744257/738084001465499789/4B389D377A94EDA747B38DF640C0B33A3A3F61AE.png", "https://media.discordapp.net/attachments/401476363707744257/738083994612006914/5302FA8FA04735224847C8BBF82D1D54C8567B9C.png" ], "bronze": [ "https://media.discordapp.net/attachments/401476363707744257/738083995211792404/719AC2C2AB5833D815C899DAF9ADB7CF11819CBA.png", "https://media.discordapp.net/attachments/401476363707744257/738083993043337276/E636A90C3F0DFFDAED0176D972AA0C73F3E40FF8.png", "https://media.discordapp.net/attachments/401476363707744257/738083997866786876/5B06D509847E0FA1405A50021486C1A5D8C6F9B2.png" ], "stone": [ "https://media.discordapp.net/attachments/401476363707744257/738083996054978730/9AC92A2FDC2996C346125296356C664373147F2F.png", "https://media.discordapp.net/attachments/401476363707744257/738083993681002586/BF3D13EACC9F44216E754884AA183185761C84CF.png", "https://media.discordapp.net/attachments/401476363707744257/738084098857238670/EA938C0B0C2AE3E6DB91514F5F8768C4F033D373.png" ] } tier = tier.lower() if tier is not None else None if tier is None or tier not in tiers: embed = Embed.create( self, ctx, title="Alliance War Badge Tiers", description="Please choose one of the tiers below :arrow_down:\nSyntax: `,awbadge <tier>`" ) normal = "\n".join([t.capitalize() for t in tiers.keys()]) embed.add_field( # Unfortunatly I have to do this to make sure that participation gets in the list :/ name="Badges", value="{}\nParticipation".format(normal) ) normal = "\n".join(tiers) return await ctx.send(embed=embed) if tier == "participation": embed = Embed.create( self, ctx, title="Participation", image="https://media.discordapp.net/attachments/401476363707744257/738083790886535228/DA7D39277836A9CF1B39A68D37EAF99999B366C7.png" ) return await ctx.send(embed=embed) if group is None: embeds = [] for i in range(3): embed = Embed.create( self, ctx, title="{} Badges".format(tier.capitalize()), image=tiers[tier][i] ) embeds.append(embed) msg = await ctx.send(embed=embeds[0]) control = menus.DEFAULT_CONTROLS if len(embeds) > 1 else { "\N{CROSS MARK}": menus.close_menu } asyncio.create_task(menus.menu(ctx, embeds, control, message=msg)) menus.start_adding_reactions(msg, control.keys()) else: embed = Embed.create( self, ctx, title="{} Badge".format(tier.capitalize()), description="{} {}".format(tier.capitalize(), group), image=tiers[tier][group_num] ) await ctx.send(embed=embed)
async def watch2gether(self, ctx, link=None): ''' Create a watch2gether room. If a link is provided then the room will be opened for that resource ''' api_keys = await self.bot.get_shared_api_tokens("watch2gether") if api_keys.get("api_key") is None: return await ctx.send( "The Watch2Gether API key has not been set. Set it with " f"`{ctx.prefix}set api watch2gether api_key,<api_key>` command" ) api_key = api_keys["api_key"] if link is None: running_rooms = await self.db.guild(ctx.guild).rooms() if running_rooms: now = datetime.utcnow() room_strs = [] i = 1 for room in running_rooms: created_at = discord.utils.snowflake_time( room["message_id"]) expires = await self.db.guild(ctx.guild).expires() if expires: if (now - created_at).total_seconds() > expires: async with self.db.guild( ctx.guild).rooms() as rooms: rooms.remove(room) running_rooms.remove(room) continue time_delta = human_timedelta(created_at, accuracy=1) string = f"[Room {i}]({room['room_url']}) (Created By - <@{room['author_id']}>, {time_delta})" i = i + 1 room_strs.append(string) if room_strs: embed_color = await ctx.embed_color() embed = discord.Embed(color=embed_color, title="Currently running rooms:") embed.description = "\n".join(room_strs) embed.set_footer( text= "Click on any of the URLs above to enter the room.\n" 'If you want to create a new room, react with "+" on this message' ) message = await ctx.send(embed=embed) emojis = [ "\N{HEAVY PLUS SIGN}", "\N{HEAVY MULTIPLICATION X}\N{VARIATION SELECTOR-16}" ] start_adding_reactions(message, emojis) pred = ReactionPredicate.with_emojis(emojis, message=message, user=ctx.author) try: await self.bot.wait_for("reaction_add", check=pred, timeout=60.0) except asyncio.exceptions.TimeoutError: return await message.clear_reactions() else: if pred.result == 1: return await message.delete() url = "https://www.watch2gether.com/rooms/create.json" data = {"api_key": api_key, "share": link} async with self.session.post(url, data=data) as resp: jsondata = await resp.json() room_key = jsondata["streamkey"] room_url = f"https://www.watch2gether.com/rooms/{room_key}" async with self.db.guild(ctx.guild).rooms() as rooms: rooms.append({ "room_key": room_key, "room_url": room_url, "message_id": ctx.message.id, "author_id": ctx.author.id }) await ctx.send(f"New Watch2Gether room created: {room_url}")
async def _cog_update(self, ctx, cog_name: InstalledCog = None): """Update all cogs, or one of your choosing.""" installed_cogs = set(await self.installed_cogs()) async with ctx.typing(): if cog_name is None: updated = await self._repo_manager.update_all_repos() else: try: updated = await self._repo_manager.update_repo( cog_name.repo_name) except KeyError: # Thrown if the repo no longer exists updated = {} updated_cogs = set(cog for repo in updated for cog in repo.available_cogs) installed_and_updated = updated_cogs & installed_cogs if installed_and_updated: await self._reinstall_requirements(installed_and_updated) await self._reinstall_cogs(installed_and_updated) await self._reinstall_libraries(installed_and_updated) message = _("Cog update completed successfully.") cognames = {c.name for c in installed_and_updated} message += _("\nUpdated: ") + humanize_list( tuple(map(inline, cognames))) else: await ctx.send(_("All installed cogs are already up to date.")) return await ctx.send(message) cognames &= set(ctx.bot.extensions.keys()) # only reload loaded cogs if not cognames: return await ctx.send( _("None of the updated cogs were previously loaded. Update complete." )) message = _("Would you like to reload the updated cogs?") can_react = ctx.channel.permissions_for(ctx.me).add_reactions if not can_react: message += " (y/n)" query: discord.Message = await ctx.send(message) if can_react: # noinspection PyAsyncCall start_adding_reactions(query, ReactionPredicate.YES_OR_NO_EMOJIS, ctx.bot.loop) pred = ReactionPredicate.yes_or_no(query, ctx.author) event = "reaction_add" else: pred = MessagePredicate.yes_or_no(ctx) event = "message" try: await ctx.bot.wait_for(event, check=pred, timeout=30) except asyncio.TimeoutError: await query.delete() return if pred.result is True: if can_react: with contextlib.suppress(discord.Forbidden): await query.clear_reactions() await ctx.invoke(ctx.bot.get_cog("Core").reload, *cognames) else: if can_react: await query.delete() else: await ctx.send(_("OK then."))
async def open_chest(ctx, user, type): if hasattr(user, "display_name"): await ctx.send("{} is opening a treasure chest. What riches lay inside?".format(user.display_name)) else: #this is when a pet is foraging. await ctx.send("{} is foraging for treasure. What will it find?".format(user[:1].upper() + user[1:])) await asyncio.sleep(2) if hasattr(user, "display_name"): luckbonus = Userdata.users[str(user.id)]['buffs'].get('luck', {'bonus':0})['bonus'] roll = random.randint(1,100)-luckbonus else: luckbonus = 0 roll = random.randint(1,100) if type == "pet": if roll <= 1: await ctx.send("{} found something precious!".format(user[:1].upper() + user[1:])) chance = Treasure.quest elif roll <= 11: chance = Treasure.unique elif roll > 11 and roll <= 50: chance = Treasure.rare elif roll > 50 and roll <= 75: chance = Treasure.common else: await ctx.send("{} found nothing of value.".format(user[:1].upper() + user[1:])) return None elif type == "normal": if roll <= 5: chance = Treasure.unique elif roll > 5 and roll <= 25: chance = Treasure.rare else: chance = Treasure.common elif type == "rare": if roll <= 15: chance = Treasure.unique elif roll > 15 and roll <= 45: chance = Treasure.rare else: chance = Treasure.common elif type == "epic": if roll <= 1: await ctx.send("This was no ordinary epic chest!") chance = Treasure.quest elif roll <= 25: chance = Treasure.unique else: chance = Treasure.rare elif type == "quest": if roll <= 10: chance = Treasure.quest else: chance = Treasure.unique itemname = random.choice(list(chance.keys())) item = chance[itemname] if item['slot'] == ['consumable']: item['uses'] = random.randint(1,item['uses']) if hasattr(user, "display_name"): await ctx.send("```css\n{} found {} ({}x).```".format(user.display_name,itemname,item['uses'])) else: await ctx.send("```css\nYour {} found {} ({}x).```".format(user,itemname,item['uses'])) msg = await ctx.send("Do you want to use, put in backpack or sell this item?") start_adding_reactions(msg, Treasure.controls.keys()) if hasattr(user, "id"): pred = ReactionPredicate.with_emojis(tuple(Treasure.controls.keys()), msg, user) else: pred = ReactionPredicate.with_emojis(tuple(Treasure.controls.keys()), msg, ctx.author) react = None try: react, user = await ctx.bot.wait_for("reaction_add", check=pred, timeout=60) except asyncio.TimeoutError: await ctx.send("Item claim timed out after one minute. Selling...") react_emoji = "💰" try: await msg.clear_reactions() except discord.Forbidden: # cannot remove all reactions for key in Treasure.controls.keys(): await msg.remove_reaction(key, ctx.bot.user) if luckbonus != 0: if Userdata.users[str(user.id)]['buffs']['luck']['duration'] <= 1: Userdata.users[str(user.id)]['buffs'].pop('luck') luckbonus = 0 else: Userdata.users[str(user.id)]['buffs']['luck']['duration'] = Userdata.users[str(user.id)]['buffs']['luck']['duration'] - 1 await Userdata.save() if react != None: react_emoji = react.emoji return {"itemname": itemname,"item":item,"equip":Treasure.controls[react_emoji]} else: if len(item["slot"]) == 2: # two handed weapons add their bonuses twice hand = "two handed" att = item["att"]*2 cha = item["cha"]*2 else: if item["slot"][0] == "right" or item["slot"][0] == "left": hand = item["slot"][0] + " handed" else: hand = item["slot"][0] + " slot" att = item["att"] cha = item["cha"] if hasattr(user, "display_name"): await ctx.send("```css\n{} found a {}. (Attack: {}, Charisma: {} [{}])```".format(user.display_name,itemname,str(att),str(cha),hand)) else: await ctx.send("```css\nYour {} found a {}. (Attack: {}, Charisma: {} [{}])```".format(user,itemname,str(att),str(cha),hand)) msg = await ctx.send("Do you want to equip, put in backpack or sell this item?") start_adding_reactions(msg, Treasure.controls.keys()) if hasattr(user, "id"): pred = ReactionPredicate.with_emojis(tuple(Treasure.controls.keys()), msg, user) else: pred = ReactionPredicate.with_emojis(tuple(Treasure.controls.keys()), msg, ctx.author) react = None try: react, user = await ctx.bot.wait_for("reaction_add", check=pred, timeout=60) except asyncio.TimeoutError: await ctx.send("Item claim timed out after one minute. Selling...") react_emoji = "💰" try: await msg.clear_reactions() except discord.Forbidden: # cannot remove all reactions for key in Treasure.controls.keys(): await msg.remove_reaction(key, ctx.bot.user) if luckbonus != 0: if Userdata.users[str(user.id)]['buffs']['luck']['duration'] <= 1: Userdata.users[str(user.id)]['buffs'].pop('luck') luckbonus = 0 else: Userdata.users[str(user.id)]['buffs']['luck']['duration'] = Userdata.users[str(user.id)]['buffs']['luck']['duration'] - 1 await Userdata.save() if react != None: react_emoji = react.emoji return {"itemname": itemname,"item":item,"equip":Treasure.controls[react_emoji]}
async def dehoist(self, ctx: commands.Context, *, role: discord.Role = None): """Decancer all members of the targeted role. Role defaults to all members of the server.""" if not await self.config.guild(ctx.guild).modlogchannel(): await ctx.send( f"Set up a modlog for this server using `{ctx.prefix}decancerset modlog #channel`" ) ctx.command.reset_cooldown(ctx) return role = role or ctx.guild.default_role guild = ctx.guild cancerous_list = [ member for member in role.members if not member.bot and self.is_cancerous(member.display_name) and ctx.me.top_role > member.top_role ] if not cancerous_list: await ctx.send(f"There's no one I can decancer in **`{role}`**.") ctx.command.reset_cooldown(ctx) return if len(cancerous_list) > 5000: await ctx.send( "There are too many members to decancer in the targeted role. " "Please select a role with less than 5000 members.") ctx.command.reset_cooldown(ctx) return member_preview = "\n".join( f"{member} - {member.id}" for index, member in enumerate(cancerous_list, 1) if index <= 10) + (f"\nand {len(cancerous_list) - 10} other members.." if len(cancerous_list) > 10 else "") case = "" if len(cancerous_list) == 1 else "s" msg = await ctx.send( f"Are you sure you want me to decancer the following {len(cancerous_list)} member{case}?\n" + box(member_preview, "py")) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await self.bot.wait_for("reaction_add", check=pred, timeout=60) except asyncio.TimeoutError: await ctx.send("Action cancelled.") ctx.command.reset_cooldown(ctx) return if pred.result is True: await ctx.send( f"Ok. This will take around **{humanize_timedelta(timedelta=timedelta(seconds=len(cancerous_list) * 1.5))}**." ) async with ctx.typing(): for member in cancerous_list: await asyncio.sleep(1) old_nick = member.display_name new_cool_nick = await self.nick_maker( guild, member.display_name) if old_nick.lower() != new_cool_nick.lower(): try: await member.edit( reason= f"Dehoist | Old name ({old_nick}): contained special characters", nick=new_cool_nick, ) except discord.Forbidden: await ctx.send( "Dehoist failed due to invalid permissions.") return except discord.NotFound: continue # else: # await self.decancer_log( # guild, member, guild.me, old_nick, new_cool_nick, "dehoist" # ) try: await ctx.send("Dehoist completed.") except (discord.NotFound, discord.Forbidden): pass else: await ctx.send("Action cancelled.") ctx.command.reset_cooldown(ctx) return
async def _trader(self, ctx: commands.Context, bypass=False): em_list = ReactionPredicate.NUMBER_EMOJIS cart = await self.config.cart_name() if await self.config.guild(ctx.guild).cart_name(): cart = await self.config.guild(ctx.guild).cart_name() text = box(_("[{} is bringing the cart around!]").format(cart), lang="css") timeout = await self.config.guild(ctx.guild).cart_timeout() if ctx.guild.id not in self._last_trade: self._last_trade[ctx.guild.id] = 0 if not bypass: if self._last_trade[ctx.guild.id] == 0: self._last_trade[ctx.guild.id] = time.time() elif self._last_trade[ctx.guild.id] >= time.time() - timeout: # trader can return after 3 hours have passed since last visit. return # silent return. self._last_trade[ctx.guild.id] = time.time() room = await self.config.guild(ctx.guild).cartroom() if room: room = ctx.guild.get_channel(room) if room is None or bypass: room = ctx self.bot.dispatch("adventure_cart", ctx) # dispatch after silent return stockcount = random.randint(3, 9) controls = {em_list[i + 1]: i for i in range(stockcount)} self._curent_trader_stock[ctx.guild.id] = (stockcount, controls) stock = await self._trader_get_items(ctx, stockcount) currency_name = await bank.get_currency_name( ctx.guild, ) if str(currency_name).startswith("<"): currency_name = "credits" for (index, item) in enumerate(stock): item = stock[index] if len(item["item"].slot) == 2: # two handed weapons add their bonuses twice hand = "two handed" att = item["item"].att * 2 cha = item["item"].cha * 2 intel = item["item"].int * 2 luck = item["item"].luck * 2 dex = item["item"].dex * 2 else: if item["item"].slot[0] == "right" or item["item"].slot[0] == "left": hand = item["item"].slot[0] + _(" handed") else: hand = item["item"].slot[0] + _(" slot") att = item["item"].att cha = item["item"].cha intel = item["item"].int luck = item["item"].luck dex = item["item"].dex text += box( _( "\n[{i}] Lvl req {lvl} | {item_name} (" "Attack: {str_att}, " "Charisma: {str_cha}, " "Intelligence: {str_int}, " "Dexterity: {str_dex}, " "Luck: {str_luck} " "[{hand}]) for {item_price} {currency_name}." ).format( i=str(index + 1), item_name=item["item"].formatted_name, lvl=item["item"].lvl, str_att=str(att), str_int=str(intel), str_cha=str(cha), str_luck=str(luck), str_dex=str(dex), hand=hand, item_price=humanize_number(item["price"]), currency_name=currency_name, ), lang="css", ) text += _("Do you want to buy any of these fine items? Tell me which one below:") msg = await room.send(text) start_adding_reactions(msg, controls.keys()) self._current_traders[ctx.guild.id] = {"msg": msg.id, "stock": stock, "users": []} timeout = self._last_trade[ctx.guild.id] + 180 - time.time() if timeout <= 0: timeout = 0 timer = await self._cart_countdown(ctx, timeout, _("The cart will leave in: "), room=room) self.tasks[msg.id] = timer try: await asyncio.wait_for(timer, timeout + 5) except asyncio.TimeoutError: await self._clear_react(msg) return with contextlib.suppress(discord.HTTPException): await msg.delete()
async def daily(self, ctx): """View the daily deals. These will come at a lower price than the store, but can only be bought once per day. Status guide: A: Available to be bought and put in backyard B: Already purchased S: Available to be bought, but will be put in stash because you either do not have the space for the, or above your level threshold""" async with self.lock: data = await self.conf.user(ctx.author).all() animals = data["animals"] animal = data["animal"] if animal in ["", "P"]: return await ctx.send("Finish starting your evolution first") multiplier = data["multiplier"] highest = max(list(map(int, animals.keys()))) e = 6 + math.ceil((multiplier - 1) * 5) display = [] deals = await self.conf.daily() for did, deal in deals.items(): status = "" amount = deal["details"]["amount"] level = deal["details"]["level"] if ctx.author.id in deal["bought"]: status = "[B]" elif (level > int(highest) - 3 and level != 1) or (amount + animals.get(str(level), 0) > e): status = "#S " else: status = " A " price = self.utils.get_total_price(level, 0, amount, False) * 0.75 display.append([ did, status, humanize_number(price), f"{amount} Level {level} {animal}{'s' if amount != 1 else ''}", ]) message = await ctx.send( f"{box(tabulate(display, tablefmt='psql'), lang='css')}Would you like to buy any of these fine animals? Click the corresponding reaction below." ) emojis = ReactionPredicate.NUMBER_EMOJIS[1:7] start_adding_reactions(message, emojis) pred = ReactionPredicate.with_emojis(emojis, message, ctx.author) try: await self.bot.wait_for("reaction_add", check=pred, timeout=60.0) except asyncio.TimeoutError: return await ctx.send( "The vendor grew uncomfortable with you there, and told you to leave and come back later." ) if ctx.author.id in self.inmarket: return await ctx.send( "Complete your current transaction or evolution first.") self.inmarket.append(ctx.author.id) buying = pred.result + 1 deal = deals[str(buying)] if ctx.author.id in deal["bought"]: # ;no self.inmarket.remove(ctx.author.id) return await ctx.send( "You already bought this deal. You cannot buy daily deals multiple times." ) level = deal["details"]["level"] amount = deal["details"]["amount"] price = self.utils.get_total_price(level, 0, amount, False) * 0.75 balance = await bank.get_balance(ctx.author) if balance < price: self.inmarket.remove(ctx.author.id) return await ctx.send( f"You need {humanize_number(price - balance)} more credits to buy that deal." ) stashing = 0 delivering = amount if level > int(highest) - 3 and level != 1: stashing = amount delivering = 0 elif amount + animals.get(str(level), 0) > e: delivering = e - animals[str(level)] stashing = amount - delivering async with self.lock: async with self.conf.user(ctx.author).all() as data: data["animals"][str(level)] = animals.get(str(level), 0) + delivering if stashing: current_stash = data["stash"]["animals"].get(str(level), 0) data["stash"]["animals"][str( level)] = current_stash + stashing self.cache[ctx.author.id] = data async with self.conf.daily( ) as data: # In case someone buys at the same time, we need to re-read the data data[str(buying)]["bought"].append(ctx.author.id) await bank.withdraw_credits(ctx.author, int(price)) await ctx.send( box( (f"[Transaction Complete]\nYou spent {humanize_number(price)} credits to buy {amount} Level {str(level)} {animal}{'s' if amount != 1 else ''}." f"\n\n{delivering} have been added to your backyard, {stashing} have been sent to your stash." ), "css", )) self.inmarket.remove(ctx.author.id)
async def ao3(self, ctx, ficlink, *, notes=""): """Returns details of a fic from a link If the fic you inputted is wrong, just click the ❎ emoji to delete the message (Needs Manage Messages permissions).""" # SET NOTES if notes == "": notes = "None." else: nlimit = await self.config.guild(ctx.guild).noteslimit() notes = notes[:nlimit] # GET URL if "chapter" in ficlink: newlink = ficlink.split("chapters")[0] ficlink = str(newlink) if "collections" in ficlink: newlink = ficlink.split("/works/")[1] ficlink = str(f"https://archiveofourown.org/works/{newlink}") if "?view_full_work=true" in ficlink: newlink = ficlink.split("?")[0] ficlink = str(newlink) firstchap = f"{ficlink}/navigate" async with self.session.get(firstchap) as ao3navigation: navigate = BeautifulSoup(await ao3navigation.text(), 'html.parser', parse_only=SoupStrainer("ol")) try: firstchap = navigate.find("li").a['href'] url = f"https://archiveofourown.org{firstchap}?view_adult=true" except AttributeError: return await ctx.send( "Error loading work info. Please ensure that the work is not locked." ) # START SCRAPING async with self.session.get(url) as ao3session: result = BeautifulSoup(await ao3session.text(), 'html.parser') # GET AUTHORS try: a = result.find_all("a", {'rel': 'author'}) author_list = [] for author in a: author_list.append(author.string.strip()) try: authors = humanize_list(deduplicate_iterables(author_list)) except Exception: authors = "Anonymous" except Exception: return await ctx.send("Error loading author list.") # GET TITLE try: preface = result.find("div", {'class': 'preface group'}).h2.string title = str(preface.strip()) except Exception: title = "No title found." # GET FANDOM try: fan = result.find("dd", {'class': 'fandom tags'}) fan_list = [] fandomlimit = await self.config.guild(ctx.guild).fandomlimit() for fandom in fan.find_all("li", limit=fandomlimit): fan_list.append(fandom.a.string) fandom = humanize_list(fan_list) except Exception: fandom = "No fandom found." # GET PAIRING try: reltags = result.find("dd", {'class': 'relationship tags'}) pair_list = [] pairlimit = await self.config.guild(ctx.guild).pairlimit() for rel in reltags.find_all("li", limit=pairlimit): pair_list.append(rel.a.string) pairing = humanize_list(pair_list) except Exception: pairing = "No Pairing." # GET CHAPTERS chapters = result.find("dd", {'class': 'chapters'}) totalchapters = str(BeautifulSoup.getText(chapters)) # GET STATUS chap_list = totalchapters.split("/") if "?" in chap_list[1]: status = "Work in Progress" elif chap_list[0] != chap_list[1]: status = "Work in Progress" else: status = "Complete" # GET RATING try: rate = result.find("dd", {'class': 'rating tags'}) rating = rate.a.string except Exception: rating = "Not Rated" # GET SUMMARY try: div = result.find("div", {'class': 'preface group'}) userstuff = div.find("blockquote", {'class': 'userstuff'}) stuff = str(BeautifulSoup.getText(userstuff)) summarytest = f"{stuff}".replace('. ', '**').replace('.', '. ') summ = f"{summarytest}".replace('**', '. \n\n') slimit = await self.config.guild(ctx.guild).sumlimit() summary = summ[:slimit] except Exception: summary = "No work summary found." # GET TAGS try: use_censor = await self.config.guild(ctx.guild).censor() freeform = result.find("dd", {'class': 'freeform tags'}) tag_list = [] taglimit = await self.config.guild(ctx.guild).taglimit() for tag in freeform.find_all("li", limit=taglimit): tag_list.append(tag.a.string) if "Explicit" in rating and use_censor: tags = f"||{(humanize_list(tag_list))}||" else: tags = humanize_list(tag_list) except Exception: tags = "No tags found." # GET DATE PUBLISHED AND UPDATED published = result.find("dd", {'class': 'published'}).string.strip() try: updated = result.find("dd", {'class': 'status'}).string.strip() except Exception: updated = published # GET LANGUAGE language = result.find("dd", {'class': 'language'}).string.strip() # GET WORDS words = int( result.find("dd", { 'class': 'words' }).string.replace(",", "")) # GET KUDOS try: kudos = int( result.find("dd", { 'class': 'kudos' }).string.replace(",", "")) except AttributeError: kudos = 0 # GET HITS try: hits = int( result.find("dd", { 'class': 'hits' }).string.replace(",", "")) except AttributeError: hits = 0 # GET WARNINGS warntags = result.find("dd", {'class': 'warning tags'}) warn_list = [] try: for warning in warntags.find_all("li"): warn_list.append(warning.a.string) warnings = humanize_list(warn_list) except Exception: warnings = "No warnings found." # CHECK INFO FORMAT use_embed = await self.config.guild(ctx.guild).embed() data = await self.config.guild(ctx.guild).formatting() if use_embed: data = discord.Embed(description=summary, title=title, url=ficlink, colour=3553599) data.add_field(name="Author:", value=authors, inline=False) data.add_field(name="Fandom:", value=fandom, inline=False) data.add_field(name="Rating:", value=rating, inline=False) data.add_field(name="Pairings:", value=pairing, inline=False) data.add_field(name="Tags:", value=tags, inline=False) data.add_field(name=f"Rec Notes by {ctx.author}: ", value=notes, inline=False) data.set_footer( text= f"Language: {language} | Words: {words} | Date Updated: {updated} | Status: {status} " ) ao3msg = await ctx.send(embed=data) else: params = { "title": title, "authors": authors, "rating": rating, "warnings": warnings, "language": language, "fandom": fandom, "pairing": pairing, "tags": tags, "summary": summary, "totalchapters": totalchapters, "status": status, "words": words, "kudos": kudos, "hits": hits, "reccer": ctx.author.mention, "notes": notes, "url": f"<{ficlink}>", "published": published, "updated": updated } ao3msg = await ctx.send(data.format(**params)) start_adding_reactions(ao3msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(ao3msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=30) await self._clear_react(ao3msg) if pred.result is False: await ao3msg.delete() return except asyncio.TimeoutError: await self._clear_react(ao3msg) autodel = await self.config.guild(ctx.guild).autodelete() try: if autodel is True: await ctx.message.delete() return except Exception: return
class Warnings_Custom(commands.Cog): """Warn misbehaving users and take automated actions.""" default_guild = { "actions": [], "reasons": {}, "allow_custom_reasons": False, "allow_context": False, "toggle_dm": True, "show_mod": False, "warn_channel": None, "toggle_channel": False, } default_member = {"total_points": 0, "status": "", "warnings": {}} def __init__(self, bot: Red): super().__init__() self.config = Config.get_conf(self, identifier=5757575755) self.config.register_guild(**self.default_guild) self.config.register_member(**self.default_member) self.bot = bot self.registration_task = self.bot.loop.create_task( self.register_warningtype()) async def red_delete_data_for_user( self, *, requester: Literal["discord_deleted_user", "owner", "user", "user_strict"], user_id: int, ): if requester != "discord_deleted_user": return all_members = await self.config.all_members() c = 0 for guild_id, guild_data in all_members.items(): c += 1 if not c % 100: await asyncio.sleep(0) if user_id in guild_data: await self.config.member_from_ids(guild_id, user_id).clear() for remaining_user, user_warns in guild_data.items(): c += 1 if not c % 100: await asyncio.sleep(0) for warn_id, warning in user_warns.get("warnings", {}).items(): c += 1 if not c % 100: await asyncio.sleep(0) if warning.get("mod", 0) == user_id: grp = self.config.member_from_ids( guild_id, remaining_user) await grp.set_raw("warnings", warn_id, "mod", value=0xDE1) # We're not utilising modlog yet - no need to register a casetype @staticmethod async def register_warningtype(): casetypes_to_register = [ { "name": "warning", "default_setting": True, "image": "\N{WARNING SIGN}\N{VARIATION SELECTOR-16}", "case_str": "Warning", }, { "name": "unwarned", "default_setting": True, "image": "\N{WARNING SIGN}\N{VARIATION SELECTOR-16}", "case_str": "Unwarned", }, ] try: await modlog.register_casetypes(casetypes_to_register) except RuntimeError: pass @commands.group() @commands.guild_only() @checks.guildowner_or_permissions(administrator=True) async def warningset(self, ctx: commands.Context): """Manage settings for Warnings.""" pass @warningset.command() @commands.guild_only() async def allowcustomreasons(self, ctx: commands.Context, allowed: bool): """Enable or disable custom reasons for a warning.""" guild = ctx.guild await self.config.guild(guild).allow_custom_reasons.set(allowed) if allowed: await ctx.send(_("Custom reasons have been enabled.")) else: await ctx.send(_("Custom reasons have been disabled.")) @warningset.command() @commands.guild_only() async def allowcontext(self, ctx: commands.Context, allowed: bool): """Enable or disable adding context to warnings.""" guild = ctx.guild await self.config.guild(guild).allow_context.set(allowed) if allowed: await ctx.send(_("Context has been enabled.")) else: await ctx.send(_("Context has been disabled.")) @warningset.command() @commands.guild_only() async def senddm(self, ctx: commands.Context, true_or_false: bool): """Set whether warnings should be sent to users in DMs.""" await self.config.guild(ctx.guild).toggle_dm.set(true_or_false) if true_or_false: await ctx.send(_("I will now try to send warnings to users DMs.")) else: await ctx.send(_("Warnings will no longer be sent to users DMs.")) @warningset.command() @commands.guild_only() async def showmoderator(self, ctx, true_or_false: bool): """Decide whether the name of the moderator warning a user should be included in the DM to that user.""" await self.config.guild(ctx.guild).show_mod.set(true_or_false) if true_or_false: await ctx.send( _("I will include the name of the moderator who issued the warning when sending a DM to a user." )) else: await ctx.send( _("I will not include the name of the moderator who issued the warning when sending a DM to a user." )) @warningset.command() @commands.guild_only() async def warnchannel(self, ctx: commands.Context, channel: discord.TextChannel = None): """Set the channel where warnings should be sent to. Leave empty to use the channel `[p]warn` command was called in. """ guild = ctx.guild if channel: await self.config.guild(guild).warn_channel.set(channel.id) await ctx.send( _("The warn channel has been set to {channel}.").format( channel=channel.mention)) else: await self.config.guild(guild).warn_channel.set(channel) await ctx.send( _("Warnings will now be sent in the channel command was used in." )) @warningset.command() @commands.guild_only() async def usewarnchannel(self, ctx: commands.Context, true_or_false: bool): """ Set if warnings should be sent to a channel set with `[p]warningset warnchannel`. """ await self.config.guild(ctx.guild).toggle_channel.set(true_or_false) channel = self.bot.get_channel(await self.config.guild(ctx.guild ).warn_channel()) if true_or_false: if channel: await ctx.send( _("Warnings will now be sent to {channel}.").format( channel=channel.mention)) else: await ctx.send( _("Warnings will now be sent in the channel command was used in." )) else: await ctx.send(_("Toggle channel has been disabled.")) @commands.group() @commands.guild_only() @checks.guildowner_or_permissions(administrator=True) async def warnaction(self, ctx: commands.Context): """Manage automated actions for Warnings. Actions are essentially command macros. Any command can be run when the action is initially triggered, and/or when the action is lifted. Actions must be given a name and a points threshold. When a user is warned enough so that their points go over this threshold, the action will be executed. """ pass @warnaction.command(name="add") @commands.guild_only() async def action_add(self, ctx: commands.Context, name: str, points: int): """Create an automated action. Duplicate action names are not allowed. """ guild = ctx.guild exceed_command = await get_command_for_exceeded_points(ctx) drop_command = await get_command_for_dropping_points(ctx) to_add = { "action_name": name, "points": points, "exceed_command": exceed_command, "drop_command": drop_command, } # Have all details for the action, now save the action guild_settings = self.config.guild(guild) async with guild_settings.actions() as registered_actions: for act in registered_actions: if act["action_name"] == to_add["action_name"]: await ctx.send(_("Duplicate action name found!")) break else: registered_actions.append(to_add) # Sort in descending order by point count for ease in # finding the highest possible action to take registered_actions.sort(key=lambda a: a["points"], reverse=True) await ctx.send( _("Action {name} has been added.").format(name=name)) @warnaction.command(name="delete", aliases=["del", "remove"]) @commands.guild_only() async def action_del(self, ctx: commands.Context, action_name: str): """Delete the action with the specified name.""" guild = ctx.guild guild_settings = self.config.guild(guild) async with guild_settings.actions() as registered_actions: to_remove = None for act in registered_actions: if act["action_name"] == action_name: to_remove = act break if to_remove: registered_actions.remove(to_remove) await ctx.tick() else: await ctx.send( _("No action named {name} exists!").format( name=action_name)) @commands.group() @commands.guild_only() @checks.guildowner_or_permissions(administrator=True) async def warnreason(self, ctx: commands.Context): """Manage warning reasons. Reasons must be given a name, description and points value. The name of the reason must be given when a user is warned. """ pass @warnreason.command(name="create", aliases=["add"]) @commands.guild_only() async def reason_create(self, ctx: commands.Context, name: str, points: int, *, description: str): """Create a warning reason.""" guild = ctx.guild if name.lower() == "custom": await ctx.send(_("*Custom* cannot be used as a reason name!")) return to_add = {"points": points, "description": description} completed = {name.lower(): to_add} guild_settings = self.config.guild(guild) async with guild_settings.reasons() as registered_reasons: registered_reasons.update(completed) await ctx.send(_("The new reason has been registered.")) @warnreason.command(name="delete", aliases=["remove", "del"]) @commands.guild_only() async def reason_del(self, ctx: commands.Context, reason_name: str): """Delete a warning reason.""" guild = ctx.guild guild_settings = self.config.guild(guild) async with guild_settings.reasons() as registered_reasons: if registered_reasons.pop(reason_name.lower(), None): await ctx.tick() else: await ctx.send(_("That is not a registered reason name.")) @commands.command() @commands.guild_only() @checks.admin_or_permissions(ban_members=True) async def reasonlist(self, ctx: commands.Context): """List all configured reasons for Warnings.""" guild = ctx.guild guild_settings = self.config.guild(guild) msg_list = [] async with guild_settings.reasons() as registered_reasons: for r, v in registered_reasons.items(): if await ctx.embed_requested(): em = discord.Embed( title=_("Reason: {name}").format(name=r), description=v["description"], ) em.add_field(name=_("Points"), value=str(v["points"])) msg_list.append(em) else: msg_list.append( _("Name: {reason_name}\nPoints: {points}\nDescription: {description}" ).format(reason_name=r, **v)) if msg_list: await menu(ctx, msg_list, DEFAULT_CONTROLS) else: await ctx.send(_("There are no reasons configured!")) @commands.command() @commands.guild_only() @checks.admin_or_permissions(ban_members=True) async def actionlist(self, ctx: commands.Context): """List all configured automated actions for Warnings.""" guild = ctx.guild guild_settings = self.config.guild(guild) msg_list = [] async with guild_settings.actions() as registered_actions: for r in registered_actions: if await ctx.embed_requested(): em = discord.Embed(title=_("Action: {name}").format( name=r["action_name"])) em.add_field(name=_("Points"), value="{}".format(r["points"]), inline=False) em.add_field( name=_("Exceed command"), value=r["exceed_command"], inline=False, ) em.add_field(name=_("Drop command"), value=r["drop_command"], inline=False) msg_list.append(em) else: msg_list.append( _("Name: {action_name}\nPoints: {points}\n" "Exceed command: {exceed_command}\nDrop command: {drop_command}" ).format(**r)) if msg_list: await menu(ctx, msg_list, DEFAULT_CONTROLS) else: await ctx.send(_("There are no actions configured!")) @commands.command() @commands.guild_only() @checks.admin_or_permissions(ban_members=True) async def warn( self, ctx: commands.Context, user: discord.Member, points: UserInputOptional[int] = 1, *, reason: str, ): """Warn the user for the specified reason. Context can be provided after running command `<points>` number of points the warning should be for. If no number is supplied 1 point will be given. Pre-set warnings disregard this. `<reason>` can be a registered reason if it exists or a custom one is created by default. """ guild = ctx.guild if user == ctx.author: return await ctx.send(_("You cannot warn yourself.")) if user.bot: return await ctx.send(_("You cannot warn other bots.")) if user == ctx.guild.owner: return await ctx.send(_("You cannot warn the server owner.")) if user.top_role >= ctx.author.top_role and ctx.author != ctx.guild.owner: return await ctx.send( _("The person you're trying to warn is equal or higher than you in the discord hierarchy, you cannot warn them." )) guild_settings = await self.config.guild(ctx.guild).all() custom_allowed = guild_settings["allow_custom_reasons"] reason_type = None async with self.config.guild( ctx.guild).reasons() as registered_reasons: if (reason_type := registered_reasons.get(reason.lower())) is None: msg = _("That is not a registered reason!") if custom_allowed: reason_type = {"description": reason, "points": points} else: # logic taken from `[p]permissions canrun` fake_message = copy(ctx.message) fake_message.content = f"{ctx.prefix}warningset allowcustomreasons" fake_context = await ctx.bot.get_context(fake_message) try: can = await self.allowcustomreasons.can_run( fake_context, check_all_parents=True, change_permission_state=False) except commands.CommandError: can = False if can: msg += " " + _( "Do `{prefix}warningset allowcustomreasons true` to enable custom " "reasons.").format(prefix=ctx.clean_prefix) return await ctx.send(msg) if reason_type is None: return # get context of reason, if provided context = "" if await self.config.guild(guild).allow_context(): msg = await ctx.send( "Would you like to provide more context to the warning? (react with yes or no)", delete_after=31) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await self.bot.wait_for("reaction_add", check=pred, timeout=30) except asyncio.TimeoutError: await ctx.send(error("Took too long, cancelling warning!"), delete_after=30) return if pred.result: done = False while not done: await ctx.send( "Please provide context as text and/or an attachment.", delete_after=240) pred = MessagePredicate.same_context(ctx) try: msg = await self.bot.wait_for("message", check=pred, timeout=240) except asyncio.TimeoutError: await ctx.send( error("Took too long, cancelling warning!"), delete_after=30) return yes_or_no = await ctx.send( "Continue with provided context? React no to redo.", delete_after=31) start_adding_reactions(yes_or_no, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(yes_or_no, ctx.author) try: await self.bot.wait_for("reaction_add", check=pred, timeout=30) except asyncio.TimeoutError: await ctx.send( error("Took too long, cancelling warning!")) return done = pred.result if len(msg.attachments): urls = "\n".join([a.url for a in msg.attachments]) context = f"{msg.content}\n**urls**: {urls}" else: context = msg.content member_settings = self.config.member(user) current_point_count = await member_settings.total_points() current_point_count += reason_type["points"] await member_settings.total_points.set(current_point_count) await warning_points_add_check(self.config, ctx, user, current_point_count) dm = guild_settings["toggle_dm"] showmod = guild_settings["show_mod"] dm_failed = False if dm: if showmod: title = _("Warning from {user}").format(user=ctx.author) else: title = _("Warning") em = discord.Embed( title=title, description=reason_type["description"], ) em.add_field(name=_("Points"), value=str(reason_type["points"])) try: await user.send( _("You have received a warning in {guild_name}.").format( guild_name=ctx.guild.name), embed=em, ) except discord.HTTPException: dm_failed = True if dm_failed: await ctx.send( _("A warning for {user} has been issued," " but I wasn't able to send them a warn message.").format( user=user.mention)) toggle_channel = guild_settings["toggle_channel"] if toggle_channel: if showmod: title = _("Warning from {user}").format(user=ctx.author) else: title = _("Warning") em = discord.Embed( title=title, description=reason_type["description"], ) em.add_field(name=_("Points"), value=str(reason_type["points"])) warn_channel = self.bot.get_channel(guild_settings["warn_channel"]) if warn_channel: if warn_channel.permissions_for(guild.me).send_messages: with contextlib.suppress(discord.HTTPException): await warn_channel.send( _("{user} has been warned.").format( user=user.mention), embed=em, ) if not dm_failed: if warn_channel: await ctx.tick() else: await ctx.send( _("{user} has been warned.").format(user=user.mention), embed=em) else: if not dm_failed: await ctx.tick() reason_msg = _( "{reason}\n\nUse `{prefix}unwarn {user} {message}` to remove this warning.{context}" ).format( reason=_("{description}\nPoints: {points}").format( description=reason_type["description"], points=reason_type["points"]), prefix=ctx.clean_prefix, user=user.id, message=ctx.message.id, context=f"\n\n**Context**:\n{context}" if context else "", ) case = await modlog.create_case( self.bot, ctx.guild, ctx.message.created_at.replace(tzinfo=timezone.utc), "warning", user, ctx.message.author, reason_msg, until=None, channel=None, ) warning_to_add = { str(ctx.message.id): { "points": reason_type["points"], "description": reason_type["description"], "mod": ctx.author.id, "date": ctx.message.created_at.replace( tzinfo=timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC"), "caseno": case.case_number, } } async with member_settings.warnings() as user_warnings: user_warnings.update(warning_to_add)
async def react( self, ctx: Context, message: discord.Message, emoji: Union[discord.Emoji, str], *, role: RoleHierarchyConverter, ): """ Create a reaction role `<message>` can be the channel_id-message_id pair from copying message ID while holding SHIFT or a message link `<emoji>` The emoji you want people to react with to get the role. `<role>` The role you want people to receive for reacting. """ if not message.guild or message.guild.id != ctx.guild.id: return await ctx.send( _("You cannot add a Reaction Role to a message not in this guild.") ) async with self.config.guild(ctx.guild).reaction_roles() as cur_setting: if isinstance(emoji, discord.Emoji): use_emoji = str(emoji.id) else: use_emoji = str(emoji).strip("\N{VARIATION SELECTOR-16}") key = f"{message.channel.id}-{message.id}-{use_emoji}" send_to_react = False try: await message.add_reaction(str(emoji).strip("\N{VARIATION SELECTOR-16}")) except discord.HTTPException: send_to_react = True if ctx.guild.id not in self.settings: self.settings[ctx.guild.id] = await self.config.guild(ctx.guild).all() self.settings[ctx.guild.id]["reaction_roles"][key] = role.id cur_setting[key] = role.id async with self.config.role(role).reactions() as reactions: reactions.append(key) await ctx.send( _("Created the reaction role {role} to {emoji} on {message}").format( role=role.name, emoji=emoji, message=message.jump_url ) ) if send_to_react: await ctx.send( _( "I couldn't add the emoji to the message. Please make " "sure to add the emoji to the message for this to work." ) ) if not await self.config.role(role).selfassignable(): msg_str = _( "{role} is not self assignable. Would you liked to make " "it self assignable and self removeable?" ).format(role=role.name, prefix=ctx.clean_prefix) msg = await ctx.send(msg_str) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=60) except asyncio.TimeoutError: return await ctx.send( _("Okay I won't automatically make {role} self assignable.").format( role=role.name ) ) if pred.result: await self.config.role(role).selfassignable.set(True) await self.config.role(role).selfremovable.set(True) await ctx.send( _("{role} has been made self assignable and self removeable.").format( role=role.name ) )
async def _cog_update(self, ctx, cog_name: InstalledCog = None): """Update all cogs, or one of your choosing.""" installed_cogs = set(await self.installed_cogs()) async with ctx.typing(): if cog_name is None: updated = await self._repo_manager.update_all_repos() else: try: updated = await self._repo_manager.update_repo(cog_name.repo_name) except KeyError: # Thrown if the repo no longer exists updated = {} updated_cogs = set(cog for repo in updated for cog in repo.available_cogs) installed_and_updated = updated_cogs & installed_cogs if installed_and_updated: await self._reinstall_requirements(installed_and_updated) await self._reinstall_cogs(installed_and_updated) await self._reinstall_libraries(installed_and_updated) message = _("Cog update completed successfully.") cognames = {c.name for c in installed_and_updated} message += _("\nUpdated: ") + humanize_list(tuple(map(inline, cognames))) else: await ctx.send(_("All installed cogs are already up to date.")) return await ctx.send(message) cognames &= set(ctx.bot.extensions.keys()) # only reload loaded cogs if not cognames: return await ctx.send( _("None of the updated cogs were previously loaded. Update complete.") ) message = _("Would you like to reload the updated cogs?") can_react = ctx.channel.permissions_for(ctx.me).add_reactions if not can_react: message += " (y/n)" query: discord.Message = await ctx.send(message) if can_react: # noinspection PyAsyncCall start_adding_reactions(query, ReactionPredicate.YES_OR_NO_EMOJIS, ctx.bot.loop) pred = ReactionPredicate.yes_or_no(query, ctx.author) event = "reaction_add" else: pred = MessagePredicate.yes_or_no(ctx) event = "message" try: await ctx.bot.wait_for(event, check=pred, timeout=30) except asyncio.TimeoutError: await query.delete() return if pred.result is True: if can_react: with contextlib.suppress(discord.Forbidden): await query.clear_reactions() await ctx.invoke(ctx.bot.get_cog("Core").reload, *cognames) else: if can_react: await query.delete() else: await ctx.send(_("OK then."))
async def command_equalizer_save(self, ctx: commands.Context, eq_preset: str = None): """Save the current eq settings to a preset.""" if not self._player_check(ctx): return await self.send_embed_msg(ctx, title=_("Nothing playing.")) dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() ) if dj_enabled and not await self._can_instaskip(ctx, ctx.author): ctx.command.reset_cooldown(ctx) return await self.send_embed_msg( ctx, title=_("Unable To Save Preset"), description=_("You need the DJ role to save equalizer presets."), ) if not eq_preset: await self.send_embed_msg( ctx, title=_("Please enter a name for this equalizer preset.") ) try: eq_name_msg = await self.bot.wait_for( "message", timeout=15.0, check=MessagePredicate.regex(fr"^(?!{re.escape(ctx.prefix)})", ctx), ) eq_preset = eq_name_msg.content.split(" ")[0].strip('"').lower() except asyncio.TimeoutError: ctx.command.reset_cooldown(ctx) return await self.send_embed_msg( ctx, title=_("Unable To Save Preset"), description=_( "No equalizer preset name entered, try the command again later." ), ) eq_preset = eq_preset or "" eq_exists_msg = None eq_preset = eq_preset.lower().lstrip(ctx.prefix) eq_presets = await self.config.custom("EQUALIZER", ctx.guild.id).eq_presets() eq_list = list(eq_presets.keys()) if len(eq_preset) > 20: ctx.command.reset_cooldown(ctx) return await self.send_embed_msg( ctx, title=_("Unable To Save Preset"), description=_("Try the command again with a shorter name."), ) if eq_preset in eq_list: eq_exists_msg = await self.send_embed_msg( ctx, title=_("Preset name already exists, do you want to replace it?") ) start_adding_reactions(eq_exists_msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(eq_exists_msg, ctx.author) await self.bot.wait_for("reaction_add", check=pred) if not pred.result: await self._clear_react(eq_exists_msg) embed2 = discord.Embed( colour=await ctx.embed_colour(), title=_("Not saving preset.") ) ctx.command.reset_cooldown(ctx) return await eq_exists_msg.edit(embed=embed2) player = lavalink.get_player(ctx.guild.id) eq = player.fetch("eq", Equalizer()) to_append = {eq_preset: {"author": ctx.author.id, "bands": eq.bands}} new_eq_presets = {**eq_presets, **to_append} await self.config.custom("EQUALIZER", ctx.guild.id).eq_presets.set(new_eq_presets) embed3 = discord.Embed( colour=await ctx.embed_colour(), title=_("Current equalizer saved to the {preset_name} preset.").format( preset_name=eq_preset ), ) if eq_exists_msg: await self._clear_react(eq_exists_msg) await eq_exists_msg.edit(embed=embed3) else: await self.send_embed_msg(ctx, embed=embed3)
async def makeDE(self, ctx, *userList): """Adds the Draft Eligible and League roles, removes Spectator role, and adds the DE prefix to every member that can be found from the userList""" empty = True added = 0 had = 0 notFound = 0 deRole = None leagueRole = None spectatorRole = None formerPlayerRole = None message = "" for role in ctx.guild.roles: if role.name == "Draft Eligible": deRole = role elif role.name == "League": leagueRole = role elif role.name == "Spectator": spectatorRole = role elif role.name == "Former Player": formerPlayerRole = role if leagueRole and deRole and spectatorRole and formerPlayerRole: break if deRole is None or leagueRole is None or spectatorRole is None or formerPlayerRole is None: await ctx.send( ":x: Couldn't find either the Draft Eligible, League, Spectator, or Former Player role in the server. Use `{0}addRequiredServerRoles` to add these roles." .format(ctx.prefix)) return for user in userList: try: member = await commands.MemberConverter().convert(ctx, user) except: message += "Couldn't find: {0}\n".format(user) notFound += 1 continue if member in ctx.guild.members: if leagueRole in member.roles: msg = await ctx.send( "{0} already has the league role, are you sure you want to make him a DE?" .format(member.mention)) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) await ctx.bot.wait_for("reaction_add", check=pred) if pred.result is False: await ctx.send("{0} not made DE.".format(member.name)) had += 1 continue else: await ctx.send( "You will need to manually remove any team or free agent roles if {0} has any." .format(member.mention)) await member.add_roles(deRole, leagueRole) added += 1 await member.edit(nick="{0} | {1}".format( "DE", self.get_player_nickname(member))) await member.remove_roles(spectatorRole, formerPlayerRole) deMessage = await self._draft_eligible_message(ctx) if deMessage: # await member.send(deMessage) await self._send_member_message(ctx, member, deMessage) empty = False if empty: message += ":x: Nobody was given the Draft Eligible role" else: message += ":white_check_mark: Draft Eligible role given to everyone that was found from list" if notFound > 0: message += ". {0} user(s) were not found".format(notFound) if had > 0: message += ". {0} user(s) already had the role or were already in the league".format( had) if added > 0: message += ". {0} user(s) had the role added to them".format(added) await ctx.send(message)
async def convert( self, ctx: Context, argument: str) -> Optional[Union[List[Dict[str, dict]], str]]: result: Optional[Union[List[Dict[str, dict]], str]] = [] team_list = await check_valid_team(argument) my_perms = ctx.channel.permissions_for(ctx.guild.me) if team_list == []: raise BadArgument('Team "{}" not found'.format(argument)) if len(team_list) == 1: result = team_list[0] else: # This is just some extra stuff to correct the team picker msg = _( "There's multiple teams with that name, pick one of these:\n") if my_perms.add_reactions and my_perms.use_external_emojis: new_msg = await ctx.send(msg) team_emojis = [ await EmojiConverter().convert(ctx, "<:" + TEAMS[team]["emoji"] + ">") for team in team_list ] log.debug(team_emojis) log.debug(team_list) pred = ReactionPredicate.with_emojis(team_emojis, message=new_msg) start_adding_reactions(new_msg, team_emojis) try: reaction, user = await ctx.bot.wait_for("reaction_add", check=pred, timeout=60) except asyncio.TimeoutError: await new_msg.edit(content=_("I guess not.")) return None else: result = team_list[pred.result] log.debug(result) else: for i, team_name in enumerate(team_list): msg += "{}: {}\n".format(i + 1, team_name) def msg_check(m): return m.author == ctx.message.author try: msg = await ctx.bot.wait_for("message", check=msg_check, timeout=60) except asyncio.TimeoutError: await new_msg.edit(content=_("I guess not.")) return None if msg.content.isdigit(): msg = int(msg.content) - 1 try: result = team_list[msg] except (IndexError, ValueError, AttributeError): pass else: return_team = None for team in team_list: if msg.content.lower() in team.lower(): return_team = team result = return_team if new_msg: await new_msg.delete() return result
async def command_queue(self, ctx: commands.Context, *, page: int = 1): """List the songs in the queue.""" async def _queue_menu( ctx: commands.Context, pages: list, controls: MutableMapping, message: discord.Message, page: int, timeout: float, emoji: str, ): if message: await ctx.send_help(self.command_queue) with contextlib.suppress(discord.HTTPException): await message.delete() return None queue_controls = { "\N{LEFTWARDS BLACK ARROW}\N{VARIATION SELECTOR-16}": prev_page, "\N{CROSS MARK}": close_menu, "\N{BLACK RIGHTWARDS ARROW}\N{VARIATION SELECTOR-16}": next_page, "\N{INFORMATION SOURCE}\N{VARIATION SELECTOR-16}": _queue_menu, } if not self._player_check(ctx): return await self.send_embed_msg( ctx, title=_("There's nothing in the queue.")) player = lavalink.get_player(ctx.guild.id) if player.current and not player.queue: arrow = await self.draw_time(ctx) pos = self.format_time(player.position) if player.current.is_stream: dur = "LIVE" else: dur = self.format_time(player.current.length) song = (await self.get_track_description( player.current, self.local_folder_current_path) or "") song += _("\n Requested by: **{track.requester}**").format( track=player.current) song += f"\n\n{arrow}`{pos}`/`{dur}`" embed = discord.Embed(title=_("Now Playing"), description=song) guild_data = await self.config.guild(ctx.guild).all() if guild_data[ "thumbnail"] and player.current and player.current.thumbnail: embed.set_thumbnail(url=player.current.thumbnail) shuffle = guild_data["shuffle"] repeat = guild_data["repeat"] autoplay = guild_data["auto_play"] text = "" text += (_("Auto-Play") + ": " + ("\N{WHITE HEAVY CHECK MARK}" if autoplay else "\N{CROSS MARK}")) text += ((" | " if text else "") + _("Shuffle") + ": " + ("\N{WHITE HEAVY CHECK MARK}" if shuffle else "\N{CROSS MARK}")) text += ( (" | " if text else "") + _("Repeat") + ": " + ("\N{WHITE HEAVY CHECK MARK}" if repeat else "\N{CROSS MARK}")) embed.set_footer(text=text) message = await self.send_embed_msg(ctx, embed=embed) dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, guild_data["dj_enabled"]) vote_enabled = guild_data["vote_enabled"] if ((dj_enabled or vote_enabled) and not await self._can_instaskip(ctx, ctx.author) and not await self.is_requester_alone(ctx)): return emoji = { "prev": "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}", "stop": "\N{BLACK SQUARE FOR STOP}\N{VARIATION SELECTOR-16}", "pause": "\N{BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR}\N{VARIATION SELECTOR-16}", "next": "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}", "close": "\N{CROSS MARK}", } expected = tuple(emoji.values()) if not player.queue and not autoplay: expected = (emoji["stop"], emoji["pause"], emoji["close"]) if player.current: task: Optional[asyncio.Task] = start_adding_reactions( message, expected[:5]) else: task: Optional[asyncio.Task] = None try: (r, u) = await self.bot.wait_for( "reaction_add", check=ReactionPredicate.with_emojis( expected, message, ctx.author), timeout=30.0, ) except asyncio.TimeoutError: return await self._clear_react(message, emoji) else: if task is not None: task.cancel() reacts = {v: k for k, v in emoji.items()} react = reacts[r.emoji] if react == "prev": await self._clear_react(message, emoji) await ctx.invoke(self.command_prev) elif react == "stop": await self._clear_react(message, emoji) await ctx.invoke(self.command_stop) elif react == "pause": await self._clear_react(message, emoji) await ctx.invoke(self.command_pause) elif react == "next": await self._clear_react(message, emoji) await ctx.invoke(self.command_skip) elif react == "close": await message.delete() return elif not player.current and not player.queue: return await self.send_embed_msg( ctx, title=_("There's nothing in the queue.")) async with ctx.typing(): limited_queue = player.queue[: 500] # TODO: Improve when Toby menu's are merged len_queue_pages = math.ceil(len(limited_queue) / 10) queue_page_list = [] async for page_num in AsyncIter(range(1, len_queue_pages + 1)): embed = await self._build_queue_page(ctx, limited_queue, player, page_num) queue_page_list.append(embed) if page > len_queue_pages: page = len_queue_pages return await menu(ctx, queue_page_list, queue_controls, page=(page - 1))
async def _save_trivia_list(self, ctx: commands.Context, attachment: discord.Attachment) -> None: """Checks and saves a trivia list to data folder. Parameters ---------- file : discord.Attachment A discord message attachment. Returns ------- None """ filename = attachment.filename.rsplit(".", 1)[0].casefold() # Check if trivia filename exists in core files or if it is a command if filename in self.trivia.all_commands or any( filename == item.stem for item in get_core_lists()): await ctx.send( _("{filename} is a reserved trivia name and cannot be replaced.\n" "Choose another name.").format(filename=filename)) return file = cog_data_path(self) / f"{filename}.yaml" if file.exists(): overwrite_message = _( "{filename} already exists. Do you wish to overwrite?").format( filename=filename) can_react = ctx.channel.permissions_for(ctx.me).add_reactions if not can_react: overwrite_message += " (y/n)" overwrite_message_object: discord.Message = await ctx.send( overwrite_message) if can_react: # noinspection PyAsyncCall start_adding_reactions(overwrite_message_object, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(overwrite_message_object, ctx.author) event = "reaction_add" else: pred = MessagePredicate.yes_or_no(ctx=ctx) event = "message" try: await ctx.bot.wait_for(event, check=pred, timeout=30) except asyncio.TimeoutError: await ctx.send(_("You took too long answering.")) return if pred.result is False: await ctx.send(_("I am not replacing the existing file.")) return buffer = io.BytesIO(await attachment.read()) yaml.safe_load(buffer) buffer.seek(0) with file.open("wb") as fp: fp.write(buffer.read()) await ctx.send( _("Saved Trivia list as {filename}.").format(filename=filename))
async def open_chest(ctx, user, type): if hasattr(user, "display_name"): await ctx.send( "{} is opening a treasure chest. What riches lay inside?". format(user.display_name)) else: await ctx.send( "{} is foraging for treasure. What will it find?".format( user[:1].upper() + user[1:])) await asyncio.sleep(2) roll = random.randint(1, 100) if type == "pet": if roll <= 5: chance = Treasure.unique elif roll > 5 and roll <= 25: chance = Treasure.rare elif roll > 25 and roll <= 75: chance = Treasure.common else: await ctx.send( "{} found nothing of value.".format(user[:1].upper() + user[1:])) return None if type == "normal": if roll <= 5: chance = Treasure.unique elif roll > 5 and roll <= 25: chance = Treasure.rare else: chance = Treasure.common if type == "rare": if roll <= 15: chance = Treasure.unique elif roll > 15 and roll <= 45: chance = Treasure.rare else: chance = Treasure.common if type == "epic": if roll <= 25: chance = Treasure.unique else: chance = Treasure.rare itemname = random.choice(list(chance.keys())) item = chance[itemname] if len(item["slot"] ) == 2: # two handed weapons add their bonuses twice hand = "two handed" att = item["att"] * 2 cha = item["cha"] * 2 else: if item["slot"][0] == "right" or item["slot"][0] == "left": hand = item["slot"][0] + " handed" else: hand = item["slot"][0] + " slot" att = item["att"] cha = item["cha"] if hasattr(user, "display_name"): await ctx.send( "{} found a {}. (Attack: {}, Charisma: {} [{}])".format( user.display_name, itemname, str(att), str(cha), hand)) else: await ctx.send( "Your {} found a {}. (Attack: {}, Charisma: {} [{}])".format( user, itemname, str(att), str(cha), hand)) msg = await ctx.send( "Do you want to equip, put in backpack or sell this item?") start_adding_reactions(msg, Treasure.controls.keys()) if hasattr(user, "id"): pred = ReactionPredicate.with_emojis( tuple(Treasure.controls.keys()), msg, user) else: pred = ReactionPredicate.with_emojis( tuple(Treasure.controls.keys()), msg, ctx.author) react, user = await ctx.bot.wait_for("reaction_add", check=pred) try: await msg.clear_reactions() except discord.Forbidden: # cannot remove all reactions for key in Treasure.controls.keys(): await msg.remove_reaction(key, ctx.bot.user) return { "itemname": itemname, "item": item, "equip": Treasure.controls[react.emoji] }
async def get_playlist_match( self, context: commands.Context, matches: MutableMapping, scope: str, author: discord.User, guild: discord.Guild, specified_user: bool = False, ) -> Tuple[Optional[Playlist], str, str]: """ Parameters ---------- context: commands.Context The context in which this is being called. matches: dict A dict of the matches found where key is scope and value is matches. scope:str The custom config scope. A value from :code:`PlaylistScope`. author: discord.User The user. guild: discord.Guild The guild. specified_user: bool Whether or not a user ID was specified via argparse. Returns ------- Tuple[Optional[Playlist], str, str] Tuple of Playlist or None if none found, original user input and scope. Raises ------ `TooManyMatches` When more than 10 matches are found or When multiple matches are found but none is selected. """ correct_scope_matches: List[Playlist] original_input = matches.get("arg") lazy_match = False if scope is None: correct_scope_matches_temp: MutableMapping = matches.get("all") lazy_match = True else: correct_scope_matches_temp: MutableMapping = matches.get(scope) guild_to_query = guild.id user_to_query = author.id correct_scope_matches_user = [] correct_scope_matches_guild = [] correct_scope_matches_global = [] if not correct_scope_matches_temp: return None, original_input, scope or PlaylistScope.GUILD.value if lazy_match or (scope == PlaylistScope.USER.value): correct_scope_matches_user = [ p for p in matches.get(PlaylistScope.USER.value) if user_to_query == p.scope_id ] if lazy_match or (scope == PlaylistScope.GUILD.value and not correct_scope_matches_user): if specified_user: correct_scope_matches_guild = [ p for p in matches.get(PlaylistScope.GUILD.value) if guild_to_query == p.scope_id and p.author == user_to_query ] else: correct_scope_matches_guild = [ p for p in matches.get(PlaylistScope.GUILD.value) if guild_to_query == p.scope_id ] if lazy_match or (scope == PlaylistScope.GLOBAL.value and not correct_scope_matches_user and not correct_scope_matches_guild): if specified_user: correct_scope_matches_global = [ p for p in matches.get(PlaylistScope.GLOBAL.value) if p.author == user_to_query ] else: correct_scope_matches_global = [ p for p in matches.get(PlaylistScope.GLOBAL.value) ] correct_scope_matches = [ *correct_scope_matches_global, *correct_scope_matches_guild, *correct_scope_matches_user, ] match_count = len(correct_scope_matches) if match_count > 1: correct_scope_matches2 = [ p for p in correct_scope_matches if p.name == str(original_input).strip() ] if correct_scope_matches2: correct_scope_matches = correct_scope_matches2 elif original_input.isnumeric(): arg = int(original_input) correct_scope_matches3 = [ p for p in correct_scope_matches if p.id == arg ] if correct_scope_matches3: correct_scope_matches = correct_scope_matches3 match_count = len(correct_scope_matches) # We done all the trimming we can with the info available time to ask the user if match_count > 10: if original_input.isnumeric(): arg = int(original_input) correct_scope_matches = [ p for p in correct_scope_matches if p.id == arg ] if match_count > 10: raise TooManyMatches( _("{match_count} playlists match {original_input}: " "Please try to be more specific, or use the playlist ID." ).format(match_count=match_count, original_input=original_input)) elif match_count == 1: return correct_scope_matches[ 0], original_input, correct_scope_matches[0].scope elif match_count == 0: return None, original_input, scope or PlaylistScope.GUILD.value # TODO : Convert this section to a new paged reaction menu when Toby Menus are Merged pos_len = 3 playlists = f"{'#':{pos_len}}\n" number = 0 correct_scope_matches = sorted(correct_scope_matches, key=lambda x: x.name.lower()) async for number, playlist in AsyncIter( correct_scope_matches).enumerate(start=1): author = self.bot.get_user( playlist.author) or playlist.author or _("Unknown") line = _("{number}." " <{playlist.name}>\n" " - Scope: < {scope} >\n" " - ID: < {playlist.id} >\n" " - Tracks: < {tracks} >\n" " - Author: < {author} >\n\n").format( number=number, playlist=playlist, scope=self.humanize_scope(playlist.scope), tracks=len(playlist.tracks), author=author, ) playlists += line embed = discord.Embed( title=_("{playlists} playlists found, which one would you like?"). format(playlists=number), description=box(playlists, lang="md"), colour=await context.embed_colour(), ) msg = await context.send(embed=embed) avaliable_emojis = ReactionPredicate.NUMBER_EMOJIS[1:] avaliable_emojis.append("🔟") emojis = avaliable_emojis[:len(correct_scope_matches)] emojis.append("\N{CROSS MARK}") start_adding_reactions(msg, emojis) pred = ReactionPredicate.with_emojis(emojis, msg, user=context.author) try: await context.bot.wait_for("reaction_add", check=pred, timeout=60) except asyncio.TimeoutError: with contextlib.suppress(discord.HTTPException): await msg.delete() raise TooManyMatches( _("Too many matches found and you did not select which one you wanted." )) if emojis[pred.result] == "\N{CROSS MARK}": with contextlib.suppress(discord.HTTPException): await msg.delete() raise TooManyMatches( _("Too many matches found and you did not select which one you wanted." )) with contextlib.suppress(discord.HTTPException): await msg.delete() return ( correct_scope_matches[pred.result], original_input, correct_scope_matches[pred.result].scope, )
async def message(self, ctx: commands.Context, *, message: str): """ Set your custom ping message. Optional Regex: `{author}`: Replaces with the authors display name. `{latency}`: Replaces with the bots latency. Example Usage: `[p]pingset message Hello {author}! My latency is {latency} ms.` Random Responses: When you specify `<message>`, you will be asked if you want to add more responses. These responses will be chosen at random when you run the ping command. To exit out of the random selection session, type `stop()` or `exit()`. """ msg = await ctx.send( "Would you like to add any other responses, to be chosen at random?" ) pred = ReactionPredicate.yes_or_no(msg, ctx.author) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) try: await self.bot.wait_for("reaction_add", check=pred, timeout=30) except asyncio.TimeoutError: await self.config.response.clear() response = await self.config.response() response.append(message) await self.config.response.set(response) return await ctx.send( "You took too long to answer, I'll stick to this one response!" ) if pred.result: await ctx.send( "Okay, let's add some random responses. Type `stop()` or `exit()` once you're done!" ) await asyncio.sleep(1) await self.config.response.clear() message_list = [message] response = await self.config.response() while True: if len(message_list) > 9: await ctx.send( "You've reached the maximum number of responses!") return await self.enum(ctx, message_list) await ctx.send("Add a random response:") def check(x): return x.author == ctx.author and x.channel == ctx.channel try: add_response = await self.bot.wait_for("message", timeout=50, check=check) except asyncio.TimeoutError: return await ctx.send( "Timed out. No changes have been made.") if add_response.content.lower().startswith( ("exit()", "stop()")): await ctx.send("Ended!") return await self.enum(ctx, message_list) else: message_list.append(add_response.content) else: await self.config.response.clear() response = await self.config.response() response.append(message) await self.config.response.set(response) return await ctx.send("Ok, I'll stick to this one response!")
async def quarall(self, ctx, quarType: int = 1, *, userSearchText: str): """Search for all usernames (not nicknames) that match a string and quarantine them Types: 1 - Normal quarantine 2 - Kick the users 3 - Ban the users """ def quarTypeText(quarTypeInt: int): if quarTypeInt == 1: return "muterole" elif quarTypeInt == 2: return "kick" elif quartypeInt == 3: return "ban" else: return None try: userSearchRegex = re.compile(userSearchText, re.I) except re.error: return await ctx.send( "Invalid search string. Format your search using Regex here and try again: https://pythex.org/" ) else: # Find the role in server muteroledata = await self.config.guild(ctx.guild).muterole() if muteroledata == "": return await ctx.send( "Be sure to set the muterole first using `setquar` :')") muterole = ctx.guild.get_role(muteroledata) # Regex search display names and usernames # Only add users who aren't muted already memberObj = ctx.guild.members matches = [] alreadyHave = 0 for memObj in memberObj: searchText = str(memObj.display_name) + " " + str(memObj.name) if re.search(userSearchRegex, searchText): if muterole not in memObj.roles: matches.append(memObj) else: alreadyHave += 1 # Return results in an embed asking if confirm desc = "Are you sure you want to {} the following users?\n\n".format( quarTypeText(quarType)) userlist = "" for user in matches: userlist += user.mention + " " + str(user.id) + "\n" desc += userlist e = discord.Embed(color=(await ctx.embed_colour()), title="Quarantine Search Results", description=desc) if alreadyHave > 0: e.set_footer(text=str(alreadyHave) + " user(s) were already quarantined and skipped.") confirmEmbed = await ctx.send(embed=e) # Wait for confirm start_adding_reactions(confirmEmbed, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(confirmEmbed, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=60) except asyncio.TimeoutError: return await ctx.send("Selection timed out.") if pred.result is True: # User responded with tick await confirmEmbed.add_reaction("⏳") print("muterole: " + str(muterole)) try: if quarType == 1: for quarUser in matches: print("Quarantine type 1/muterole against " + str(quarUser.display_name)) await quarUser.edit(roles=[muterole]) elif quarType == 2: for quarUser in matches: print("Quarantine type 2/kick against " + str(quarUser.display_name)) await ctx.guild.kick(quarUser) elif quarType == 3: for quarUser in matches: print("Quarantine type 3/ban against " + str(quarUser.display_name)) await ctx.guild.ban(quarUser) except: return await ctx.send( "Please confirm that I have permissions to manage members + manage roles...." ) else: await confirmEmbed.add_reaction("💯") # Send report to channel destinationdata = await self.config.guild(ctx.guild).report() if destinationdata == "": return else: e2 = discord.Embed(color=(await ctx.embed_colour()), title="Quarantined: " + quarTypeText(quarType), description=userlist) e2.set_footer(text="Sent in #{}".format(ctx.channel)) destination = ctx.guild.get_channel(destinationdata) await destination.send(embed=e2) else: # User responded with cross return await ctx.send("Exited quarantine")
async def create_poll(self, ctx, *, args: str): """Créer un sondage dynamique avec réactions **Format :** `poll Question ?;Réponse 1;Réponse 2;Réponse N...` __Options__ `-exp X` = Modifier la durée (en minutes) après lequel le sondage expire (par def. 10m) `-image URL` = Ajouter une image au sondage `-pin` = Epingler/désépingler auto. le sondage `-nostats` = Désactiver les statistiques en direct (elles s'afficheront quand même à la fin) `-anonymous` = Ne pas afficher le créateur du sondage""" author, channel = ctx.author, ctx.channel letters = [u for u in '🇦🇧🇨🇩🇪🇫🇬🇭🇮'] exp = 10 anonyme = False pin = False dispstats = True sus = False emcolor = discord.Color.random() em = discord.Embed(color=emcolor) opts = re.compile(r'-(\w*)(?:\s?([\w:\/\.?=&\-]*))?', re.DOTALL | re.IGNORECASE).findall(args) if opts: args = args.split('-')[0] for opt, val in opts: if opt.lower() == 'image': em.set_image(url=val) elif opt.lower() == 'exp': try: exp = int(val) except Exception: pass if exp < 1 or exp > 720: return ctx.reply( "**Temps invalide** › Le sondage ne peut durer qu'entre 1 et 720m (12h)", mention_author=False) else: pass elif opt.lower() in ('anonymous', 'anonyme'): anonyme = True elif opt.lower() == 'pin': pin = True elif opt.lower() in ('nostats', 'nostat'): dispstats = False elif opt.lower() == 'sus': sus = True q, *r = [i.strip() for i in re.split(';|-', args)] if not r: r = ('Pour/Oui', 'Contre/Non') emojis = ['👍', '👎'] elif len(r) <= 9: emojis = letters[:len(r)] else: return await ctx.reply( "**Trop de réponses possibles** › Vous ne pouvez mettre que 9 réponses possibles au maximum.", mention_author=False) polls = await self.config.channel(channel).Polls() if polls: poll_id = max([polls[n]['id'] for n in polls]) + 1 else: poll_id = 1 reps = {i: emojis[r.index(i)] for i in r} stats = {i: [] for i in r} em.timestamp = datetime.utcnow() + timedelta(minutes=exp) em.title = f'`#{poll_id}` · ***{q}***' em.description = "\n".join([ f'{reps[p]} › **{p}** (0%)' for p in reps ]) if dispstats else "\n".join([f'{reps[p]} › **{p}**' for p in reps]) if not anonyme: em.set_footer(text=author.name, icon_url=author.avatar_url) if sus: # Easter-egg AMONGUSSSSS em.set_footer( text='Imposteur', icon_url='https://cdn2.clc2l.fr/t/A/m/Among-Us-oAEaxX.png') poll_data = { 'embed': em.to_dict(), 'reps': reps, 'stats': stats, 'id': poll_id, 'exp': time.time() + (60 * exp), 'disp_stats': dispstats, 'pin': pin } msg = await ctx.send(embed=em) await self.config.channel(channel).Polls.set_raw(msg.id, value=poll_data) start_adding_reactions(msg, emojis) if pin: try: await msg.pin() except Exception: await ctx.send( "Impossible d'épingler auto. › Je n'ai pas les permissions nécessaires (`Gestion des messages`)" )