async def slash_edit(client, event): """Editing slashes, bakana!""" yield InteractionResponse(embed=Embed('Choose your poison.')) await sleep(2.0, KOKORO) yield InteractionResponse(embed=Embed('Choose your cake.'), message=None) await sleep(2.0, KOKORO) yield InteractionResponse(embed=Embed('Choose your neko.'), message=None)
async def add_emoji(client, event, emoji: ('str', 'The emoji to add.'), name: ('str', 'Custom name to add the emoji with.') = None): """Adds an emoji to the guild.""" if not client.is_owner(event.user): abort('Owner only!') emoji = parse_emoji(emoji) if emoji is None: abort('That\'s not an emoji.') if emoji.is_unicode_emoji(): abort('Cannot add unicode emojis') if name is None: name = emoji.name else: if len(name) > 32: abort('Name length can be max 32.') embed = Embed('Are you sure to add this emoji?').add_field( 'Name:', name).add_image(emoji.url) message = yield InteractionResponse(embed=embed, components=ADD_EMOJI_COMPONENTS) try: component_interaction = await wait_for_component_interaction( message, timeout=300.0, check=functools.partial(check_is_user_same, event.user)) except TimeoutError: component_interaction = None cancelled = True else: if component_interaction.interaction == ADD_EMOJI_BUTTON_CANCEL: cancelled = True else: cancelled = False if cancelled: embed.title = 'Adding emoji has been cancelled.' else: embed.title = 'Emoji has been added!' async with client.http.get(emoji.url) as response: emoji_data = await response.read() await client.emoji_create(event.guild, name, emoji_data) yield InteractionResponse(embed=embed, components=None, message=message, event=component_interaction)
async def latest_users(event): """Shows the new users of the guild.""" date_limit = datetime.now() - timedelta(days=7) users = [] guild = event.guild for user in guild.users.values(): # `joined_at` might be set as `None` if the user is a lurker. # We can ignore lurkers, so use `created_at` which defaults to Discord epoch. created_at = user.guild_profiles[guild].created_at if created_at > date_limit: users.append((created_at, user)) users.sort(reverse=True) del users[10:] embed = Embed('Recently joined users') if users: for index, (joined_at, user) in enumerate(users, 1): created_at = user.created_at embed.add_field( f'{index}. {user.full_name}', f'Id: {user.id}\n' f'Mention: {user.mention}\n' '\n' f'Joined : {joined_at:{DATETIME_FORMAT_CODE}} [*{elapsed_time(joined_at)} ago*]\n' f'Created : {created_at:{DATETIME_FORMAT_CODE}} [*{elapsed_time(created_at)} ago*]\n' f'Difference : {elapsed_time(relativedelta(created_at, joined_at))}', ) else: embed.description = '*none*' return InteractionResponse(embed=embed, allowed_mentions=None)
async def latest_users( client, event, ): """Shows the new users of the guild.""" if not event.user.has_role(ROLE__NEKO_DUNGEON__MODERATOR): abort('Hacker trying to hack Discord.') date_limit = datetime.now() - timedelta(days=7) users = [] guild = event.guild for user in guild.users.values(): # Use created at and not `joined_at`, we can ignore lurkers. created_at = user.guild_profiles[guild].created_at if created_at > date_limit: users.append((created_at, user)) users.sort(reverse=True) del users[10:] embed = Embed('Recently joined users') if users: for index, (joined_at, user) in enumerate(users, 1): add_user_field(embed, index, joined_at, user) else: embed.description = '*none*' return InteractionResponse(embed=embed, allowed_mentions=None)
async def __call__(self, command_name): command_source = self.sources.get(command_name, None) if (command_source is None): abort(f'Command not found: {command_name!r}') page = command_source[0] if len(command_source) > 1: components = Row( Button( emoji=EMOJI_LEFT, custom_id=f'source.{self.command_type}.{command_name}._', enabled=False, ), Button( emoji=EMOJI_RIGHT, custom_id=f'source.{self.command_type}.{command_name}.1', enabled=True, ), ) else: components = None yield InteractionResponse(page, components=components)
async def handle_guild_banner(event): guild = event.guild if (guild is not None): embed = create_embed(guild, 'banner', guild.banner_url_as(size=4096), guild.banner_hash) return InteractionResponse(embed=embed, components=GUILD_ICON_BANNER_COMPONENTS)
async def nine(): components = [ [Button(f'{index_1}x{index_2}', custom_id=f'nine.{index_1}.{index_2}') for index_1 in range(1, 4)] for index_2 in range(1, 4) ] yield InteractionResponse('Select a nyan', components=components)
async def handle_guild_icon(event): guild = event.guild if (guild is not None): embed = create_embed(guild, 'icon', guild.icon_url_as(size=4096), guild.icon_hash) return InteractionResponse(embed=embed, components=GUILD_ICON_ICON_COMPONENTS)
async def cat_feeder(): """Hungry cat feeder!""" return InteractionResponse( f'Please feed my cat {CAT_FEEDER_CAT_EMOJI:e}, she is hungry.', components=Button('Feed cat', CAT_FEEDER_FOOD_EMOJI, custom_id=CAT_FEEDER_CUSTOM_ID, style=ButtonStyle.green))
async def handle_guild_invite_splash(event): guild = event.guild if (guild is not None): embed = create_embed(guild, 'invite splash', guild.invite_splash_url_as(size=4096), guild.invite_splash_hash) return InteractionResponse( embed=embed, components=GUILD_ICON_INVITE_SPLASH_COMPONENTS)
async def handle_guild_discovery_splash(event): guild = event.guild if (guild is not None): embed = create_embed(guild, 'discovery splash', guild.discovery_splash_url_as(size=4096), guild.discovery_splash_hash) return InteractionResponse( embed=embed, components=GUILD_ICON_DISCOVERY_SPLASH_COMPONENTS)
async def ping_pong(): if random() < 0.5: button = BUTTON_PING else: button = BUTTON_PONG return InteractionResponse(f'**ping {EMOJI_PING_PONG:e} pong**', components=button)
async def switch_page(event, command_type, command_name, page_index): # Check for the same user if event.message.interaction.user is not event.user: return # Check whether page is really valid. if not page_index.isdigit(): return # Lookup command source command_type_commands = COLLECTED_COMMANDS.get(command_type, None) if command_type_commands is None: return command_source = command_type_commands.get(command_name, None) if command_source is None: return page_index = int(page_index) if page_index == 0: button_back_enabled = False button_back_page_index = '_' button_next_enabled = True button_next_page_index = str(page_index + 1) elif page_index > len(command_source) - 2: page_index = len(command_source) - 1 button_back_enabled = True button_back_page_index = str(page_index - 1) button_next_enabled = False button_next_page_index = '_' else: button_back_enabled = True button_back_page_index = str(page_index - 1) button_next_enabled = True button_next_page_index = str(page_index + 1) page = command_source[page_index] components = Row( Button( emoji=EMOJI_LEFT, custom_id= f'source.{command_type}.{command_name}.{button_back_page_index}', enabled=button_back_enabled, ), Button( emoji=EMOJI_RIGHT, custom_id= f'source.{command_type}.{command_name}.{button_next_page_index}', enabled=button_next_enabled, ), ) return InteractionResponse(page, components=components)
async def waifu(): """Ships waifus!""" embed = Embed('Please select a waifu type to ship.') select = Select( [Option(waifu_type, waifu_type) for waifu_type in WAIFU_TYPES], custom_id=WAIFU_CUSTOM_ID, ) return InteractionResponse(embed=embed, components=select)
async def role_claimer(event): """Role claimer message. (Owner only)""" # Double check. if not event.user.has_role(ROLE_OWNER): abort('Owner only') return InteractionResponse('Claim role by clicking on it', components=ROLE_CLAIMER_COMPONENTS)
async def pick(client, event): """Picks who I like the most from the attenders.""" users = [event.user] message = yield InteractionResponse(render_joined_users(users), allowed_mentions=None, components=BUTTON_ATTEND) try: async for component_interaction in iter_component_interactions( message, timeout=60.0, check=functools.partial(check_is_user_unique, users)): users.append(component_interaction.user) # limit the amount of users to 10. if len(users) == 10: break yield InteractionResponse(render_joined_users(users), allowed_mentions=None, event=component_interaction) except TimeoutError: component_interaction = None most_liked = pick_most_liked(client, users) content_parts = ['From:'] for user in users: content_parts.append('\n') content_parts.append(user.mention) content_parts.append('\n\nI like ') content_parts.append(most_liked.mention) content_parts.append(' the most.') content = ''.join(content_parts) yield InteractionResponse(content, allowed_mentions=most_liked, components=None, message=message, event=component_interaction)
async def link(): """Melo Melo!""" component = Button( 'melo melo', url='https://www.youtube.com/watch?v=gYGqcORGqIw&ab_channel=ShoopTouhouEurobeatShoopTouhouEurobeat', ) return InteractionResponse('_ _', components = component, show_for_invoking_user_only = True, )
async def we_gucci(client, event): """Getting there.""" components = [ Button('cake', custom_id='cake', style=ButtonStyle.violet), Button('cat', custom_id='cat', style=ButtonStyle.gray), Button('snake', custom_id='snake', style=ButtonStyle.green), Button('eggplant', custom_id='eggplant', style=ButtonStyle.red), ] yield InteractionResponse(embed=Embed('Choose your poison.'), components=components) try: async for component_interaction in iter_component_interactions(event, timeout=10.0, count=3): emoji = BUILTIN_EMOJIS[component_interaction.interaction.custom_id] yield InteractionResponse(emoji.as_emoji, components=components, event=component_interaction) except TimeoutError: await client.interaction_response_message_edit(event, components=None) return yield InteractionResponse(embed=Embed('Choose your poison.', 'Interaction exhausted.'), components=None, message=event.message)
def list_per_client_get_response(selected_client, clients): description_parts = [] extensions = selected_client.extensions limit = len(extensions) if limit: index = 0 while True: extension = extensions[index] index += 1 description_parts.append('`') description_parts.append(extension.name) description_parts.append('`') if index == limit: break description_parts.append('\n') continue else: description_parts.append('*No extension detected.*') description = ''.join(description_parts) # Keep title building like this. Later we might alter it. title_parts = [] title_parts.append('Extensions of ') title_parts.append(selected_client.full_name) title_parts.append(' (') title_parts.append(str(selected_client.id)) title_parts.append(')') title = ''.join(title_parts) embed = Embed(title, description).add_thumbnail(selected_client.avatar_url) components = Select( [ Option(str(client.id), client.full_name, default=(client is selected_client)) for client in clients ], custom_id=EXTENSION_LIS_PER_GUILD_CUSTOM_ID, placeholder='Select a client', ) return InteractionResponse(embed=embed, components=components)
async def collect_reactions(client, event): """Collects reactions""" message = yield InteractionResponse('Collecting reactions for 1 minute!') await sleep(60.0) reactions = message.reactions if reactions: emojis = list(reactions) # Limit reactions to 16 to avoid error from Discord del emojis[16:] yield ' '.join(emoji.as_emoji for emoji in emojis) else: yield 'No reactions were collected.'
async def zoo(event): """Visiting zoo!""" message = yield InteractionResponse('Please select animals to visit!', components=ZOO_SELECT) try: component_interaction = await wait_for_component_interaction( message, timeout=300.0, check=functools.partial(check_is_user_same, event.user)) except TimeoutError: content = 'You didn\'t decide which animals to visit and the zoo closed, see ya tomorrow!' component_interaction = None else: selected_animals = component_interaction.interaction.options if selected_animals is None: content = 'Going to zoo only to buy icecream?' else: content_parts = ['Visiting animals in the zoo!'] for selected_animal in selected_animals: try: description = ANIMAL_IDENTIFIER_TO_DESCRIPTION[ selected_animal] except KeyError: continue content_parts.append(description) content = '\n\n'.join(content_parts) yield InteractionResponse(content, components=None, message=message, event=component_interaction)
async def select_test(): main_component = [ Select([ Option('cake', 'cake', default=True), Option('cat', 'cat'), Option('sugoi', 'sugoi'), ], placeholder = 'dunno', min_values = 2, max_values = 3, ), ] yield InteractionResponse(embed=Embed('Choose your poison.'), components=main_component, show_for_invoking_user_only=True)
async def events(): """Event related role information.""" embed = Embed('Event roles', color=COLOR__KOISHI_HELP, ).add_field( ROLE__NEKO_DUNGEON__EVENT_PARTICIPANT.name, f'{ROLE__NEKO_DUNGEON__EVENT_PARTICIPANT.mention} are participant of the actual event.' ).add_field( ROLE__NEKO_DUNGEON__EVENT_WINNER.name, f'{ROLE__NEKO_DUNGEON__EVENT_WINNER.mention} won already an event. It is set in stone, only a couple of ' f'chads may achieve this level of power.' ).add_field( ROLE__NEKO_DUNGEON__EVENT_MANAGER.name, f'{ROLE__NEKO_DUNGEON__EVENT_MANAGER.mention} are managing the actual event. Hoping our god ZUN will ' f'notice them one day.' ) return InteractionResponse(embed=embed, allowed_mentions=None)
async def rules(client, event): """Neko Dungeon\'s rules!""" embed = Embed(f'Rules of {GUILD__NEKO_DUNGEON}:', color=COLOR__KOISHI_HELP, ).add_field( '0. Guidelines', 'Follow [Discord\'s guidelines](https://discord.com/guidelines)', ).add_field( '1. Behaviour', 'Listen to staff and follow their instructions.', ).add_field( '2. Language', f'{GUILD__NEKO_DUNGEON} is an english speaking server, please try to stick yourself to it.', ).add_field( '3. Channels', 'Read the channel\'s topics. Make sure to keep the conversations in their respective channels.' ).add_field( '4. Usernames', 'Invisible, offensive or noise unicode names are not allowed.' ).add_field( '5. Spamming', 'Forbidden in any form. Spamming server members in DM-s counts as well.', ).add_field( '6. NSFW', 'Keep explicit content in nsfw channels.', ).add_field( '7. Roles', f'Do not beg for roles. You can claim {ROLE__NEKO_DUNGEON__VERIFIED.mention} role, what gives you access to ' f'additional channels by typing `nya` at {CHANNEL__NEKO_DUNGEON__SYSTEM.mention}.\n' f'*You must be the member of the guild for at least 10 minutes and {client.mention} must be online ' f'as well.*' ).add_field( '8. Advertisements', 'Advertising other social medias, servers, communities or services in chat or in DM-s are disallowed.' ).add_field( '9. No political or religious topics.', 'I do not say either that aliens exists, even tho they do.', ).add_field( '10. Alternative accounts', 'Instant ban.' ).add_field( '11. Deep frying fumos', 'Fumo frying is bannable offense.' ) return InteractionResponse(embed=embed, allowed_mentions=None)
async def getting_good(client, event): """Getting there.""" main_component = Row( Button('cake', custom_id='cake', style=ButtonStyle.violet), Button('cat', custom_id='cat', style=ButtonStyle.gray), Button('snake', custom_id='snake', style=ButtonStyle.green), Button('eggplant', custom_id='eggplant', style=ButtonStyle.red), Button('eggplant', custom_id='eggplant', style=ButtonStyle.red, enabled=False), ) yield InteractionResponse(embed=Embed('Choose your poison.'), components=main_component, show_for_invoking_user_only=True) try: component_interaction = await wait_for_component_interaction(event, timeout=4500., check=partial_func(check_user, event.user)) except TimeoutError: await client.message_edit(event.message, components=None) else: emoji = BUILTIN_EMOJIS[component_interaction.interaction.custom_id] await client.message_edit(event.message, emoji.as_emoji, embed=None, components=None)
async def guild_icon( event, choice: (GUILD_ICON_CHOICES, 'Which icon of the guild?') = 'icon', ): """Shows the guild's icon.""" guild = event.guild if (guild is None) or guild.partial: abort( 'The command unavailable in guilds, where the application\'s bot is not in.' ) if choice == 'icon': name = 'icon' url = guild.icon_url_as(size=4096) hash_value = guild.icon_hash components = GUILD_ICON_ICON_COMPONENTS elif choice == 'banner': name = 'banner' url = guild.banner_url_as(size=4096) hash_value = guild.banner_hash components = GUILD_ICON_BANNER_COMPONENTS elif choice == 'discovery_splash': name = 'discovery splash' url = guild.discovery_splash_url_as(size=4096) hash_value = guild.discovery_splash_hash components = GUILD_ICON_ICON_COMPONENTS else: name = 'invite splash' url = guild.invite_splash_url_as(size=4096) hash_value = guild.invite_splash_hash components = GUILD_ICON_DISCOVERY_SPLASH_COMPONENTS embed = create_embed(guild, name, url, hash_value) return InteractionResponse(embed=embed, components=components)
async def __call__( self, client, event, message: ('str', 'Additional message to send?') = '', ): # Check for permissions! guild = event.guild if guild is None: abort('Guild only command') if guild not in client.guild_profiles: abort('I must be in the guild to execute this command.') image = random_with_tag(self.tag_id) if image is None: abort(f'No {self.name_form__ing} image is added :\'C') if message: first_word = event.user.name last_word = sanitize_mentions(message, event.guild) # Security goes brrr if len(last_word) > 200: last_word = last_word[:200] + ' ...' else: first_word = client.name last_word = event.user.name title = f'{first_word} {self.name_form__s} {last_word}.' embed = Embed(title, color=(event.id>>22)&0xffffff) \ .add_image(f'attachment://{os.path.basename(image.path)}') file = await ReuAsyncIO(join(IMAGE_PATH, image.path)) return InteractionResponse(embed=embed, file=file)
async def claimable(): """A list of claimable roles in ND.""" embed = Embed('Claimable roles:', f'Claim roles by typing a specific text at {CHANNEL__NEKO_DUNGEON__SYSTEM.mention}!', color=COLOR__KOISHI_HELP, ).add_field( ROLE__NEKO_DUNGEON__VERIFIED.name, f'Claim {ROLE__NEKO_DUNGEON__VERIFIED.mention} by typing `nya`!', ).add_field( ROLE__NEKO_DUNGEON__ANNOUNCEMENTS.name, f'Claim {ROLE__NEKO_DUNGEON__ANNOUNCEMENTS.mention} by typing `i meow`, or un-claim by `i not meow`!', ).add_field( ROLE__NEKO_DUNGEON__ELEVATED.name, f'Claim {ROLE__NEKO_DUNGEON__ELEVATED.mention} by typing `nekogirl`!' f'\n' f'*You must be in the guild for at least half year + have {ROLE__NEKO_DUNGEON__VERIFIED.mention} role*.', ).add_field( ROLE__NEKO_DUNGEON__NSFW_ACCESS.name, f'Claim {ROLE__NEKO_DUNGEON__NSFW_ACCESS.mention} by typing `i am horny`, or un-claim by `no sex`!' f'\n' f'*You must have {ROLE__NEKO_DUNGEON__VERIFIED.mention} role*.', ) return InteractionResponse(embed=embed, allowed_mentions=None)
async def edit_( client, event, sticker_name: ('str', 'The sticker\'s name to delete', 'sticker'), new_name: ( 'str', 'New name for the sticker', ) = None, new_emoji_value: (str, 'Emoji representation of the sticker.', 'new_emoji') = None, new_description: (str, 'Description for the sticker.') = None, ): """Edits the given sticker. (You must have emoji-council role)""" if not event.user.has_role(ROLE__NEKO_DUNGEON__EMOJI_MANAGER): abort( f'You must have {ROLE__NEKO_DUNGEON__EMOJI_MANAGER:m} role to invoke this command.' ) sticker = event.guild.get_sticker_like(sticker_name) if (sticker is None): abort(f'No sticker matched the given name: {sticker_name!r}.') anything_to_edit = False if (new_name is not None): if (sticker.name != new_name): name_length = len(new_name) if (name_length < 2) or (name_length > 32): abort( f'Sticker name\'s length can be in range [2:32], got {name_length!r}, {new_name!r}.' ) anything_to_edit = True else: new_name = None if (new_emoji_value is not None): new_emoji = parse_emoji(new_emoji_value) if new_emoji is None: abort(f'{new_emoji_value} cannot be interpreted as an emoji.') if new_emoji.is_custom_emoji(): abort(f'Only unicode can be used, got {new_emoji:e}') tags = sticker.tags if (tags is None) or (len(tags) != 1) or (next(iter(tags)) != new_emoji.name): anything_to_edit = True else: new_emoji = None else: new_emoji = None if (new_description is not None): description_length = len(new_description) if (description_length > 100): abort( f'Sticker description\'s length can be in range [0:100], got {description_length!r}, ' f'{new_description!r}.') if (sticker.description != new_description): anything_to_edit = True else: new_description = None if not anything_to_edit: abort('No differences were provided.') embed = Embed('Confirmation', f'Are you sure to edit {sticker.name!r} sticker?') if (new_name is not None): embed.add_field('Name', f'{sticker.name} -> {new_name}') if (new_emoji is not None): embed.add_field('Tags', f'{", ".join(sticker.tags)} -> {new_emoji.name}') if (new_description is not None): embed.add_field('Description', f'{sticker.description} -> {new_description}') message = yield InteractionResponse(embed=embed, components=STICKER_EDIT_COMPONENTS, allowed_mentions=None) try: component_interaction = await wait_for_component_interaction( message, timeout=300.0, check=partial_func(check_sticker_editor, event.user)) except TimeoutError: embed.title = 'Timeout' embed.description = f'Sticker {sticker.name!r} was not edited.' # Edit the source message with the source interaction yield InteractionResponse(embed=embed, components=None, allowed_mentions=None, message=message) return if component_interaction.interaction == STICKER_EDIT_BUTTON_CANCEL: embed.title = 'Cancelled' embed.description = f'Sticker {sticker.name!r} was not edited.' # Edit the source message with the component interaction yield InteractionResponse(embed=embed, components=None, allowed_mentions=None, event=component_interaction) return # Acknowledge the event await client.interaction_component_acknowledge(component_interaction) kwargs = {} if (new_name is not None): kwargs['name'] = new_name if (new_emoji is not None): kwargs['emoji_representation'] = new_emoji if (new_description is not None): kwargs['description'] = new_description try: await client.sticker_guild_edit(sticker, **kwargs) except ConnectionError: # No internet, let it be return except DiscordException as err: if err.code == ERROR_CODES.unknown_sticker: failure = False else: failure = True embed.title = 'Failure' embed.description = repr(err) else: failure = False if not failure: embed.title = 'Success' embed.description = f'Sticker {sticker.name!r} has been successfully edited.' # Edit the source message yield InteractionResponse(embed=embed, message=message, components=None)
async def delete_( client, event, sticker_name: ('str', 'The sticker\'s name to delete', 'sticker'), ): """Deletes the given sticker. (You must have emoji-council role)""" if not event.user.has_role(ROLE__NEKO_DUNGEON__EMOJI_MANAGER): abort( f'You must have {ROLE__NEKO_DUNGEON__EMOJI_MANAGER:m} role to invoke this command.' ) sticker = event.guild.get_sticker_like(sticker_name) if (sticker is None): abort(f'No sticker matched the given name: {sticker_name!r}.') embed = Embed( 'Confirmation', f'Are you sure to delete {sticker.name!r} ({sticker.id}) sticker forever?' ) message = yield InteractionResponse(embed=embed, components=STICKER_DELETE_COMPONENTS, allowed_mentions=None) try: component_interaction = await wait_for_component_interaction( message, timeout=300.0, check=partial_func(check_sticker_deleter, event.user)) except TimeoutError: embed.title = 'Timeout' embed.description = f'Sticker {sticker.name!r} was not deleted.' # Edit the source message with the source interaction yield InteractionResponse(embed=embed, components=None, allowed_mentions=None, message=message) return if component_interaction.interaction == STICKER_DELETE_BUTTON_CANCEL: embed.title = 'Cancelled' embed.description = f'Sticker {sticker.name!r} was not deleted.' # Edit the source message with the component interaction yield InteractionResponse(embed=embed, components=None, allowed_mentions=None, event=component_interaction) return # Acknowledge the event await client.interaction_component_acknowledge(component_interaction) try: await client.sticker_guild_delete(sticker) except ConnectionError: # No internet, let it be return except DiscordException as err: if err.code == ERROR_CODES.unknown_sticker: failure = False else: failure = True embed.title = 'Failure' embed.description = repr(err) else: failure = False if not failure: embed.title = 'Success' embed.description = f'Sticker {sticker.name!r} has been deleted successfully.' # Edit the source message yield InteractionResponse(embed=embed, message=message, components=None)