async def is_banned(client, event, user: ('user', 'Who should I check?')): """Checks whether the user is banned.""" if (not event.user.has_role(ROLE__NEKO_DUNGEON__TESTER)) and ( not event.user_permissions.can_ban_users): abort('You need to have `ban users` permissions to do this.') if not event.channel.cached_permissions_for(client).can_ban_users: abort('I need to have `ban users` permissions to do this.') yield # acknowledge the event try: ban_entry = await client.guild_ban_get(event.guild, user) except DiscordException as err: if err.code == ERROR_CODES.unknown_ban: ban_entry = None else: raise embed = Embed(f'Ban entry for {user:f}').add_thumbnail(user.avatar_url) if ban_entry is None: embed.description = 'The user **NOT YET** banned.' else: embed.description = 'The user is banned.' reason = ban_entry.reason if reason is None: reason = '*No reason was specified.*' embed.add_field('Reason:', reason) yield embed
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 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 show_auto_react_roles(client, message): guild = message.guild if guild is None: return managers = client.events.guild_delete.get_waiters(guild, AutoReactRoleManager, by_type=True, is_method=True) embed = Embed(f'Auto role managers for: {guild}', color=AUTO_REACT_ROLE_COLOR) if not managers: embed.description = '*none*' await Pagination(client, message.channel, [embed]) return results = [] for manager in managers: message_ = manager.message title = f'{message_.channel:m} {message.id}' results.append((title, manager), ) await ChooseMenu(client, message.channel, results, select_auto_react_role_gui, embed=embed, prefix='¤')
async def queue(client, message): guild = message.guild if guild is None: return voice_client = client.voice_client_for(message) color = VOICE_COLORS.get(client) title = f'Playing queue for {guild}' page = Embed(title, color=color) pages = [page] while True: if voice_client is None: page.description = '*none*' break source = voice_client.source if (source is not None): page.add_field('Actual:', source.title) queue = voice_client.queue limit = len(queue) if limit: index = 0 while True: source = queue[index] index += 1 page.add_field(f'Track {index}.:', source.title) if index == limit: break if index%10 == 0: page = Embed(title, color=color) pages.append(page) else: if source is None: page.description = '*none*' break await Pagination(client, message.channel, pages)
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)
async def user_top( event, user: ('user', 'By who?') = None, count: (range(10, 61, 10), 'The maximal amount of emojis to show') = 30, months: (range(1, 13), 'The months to get') = 1, ): """List the most used stickers at ND by you or by the selected user.""" if user is None: user = event.user async with DB_ENGINE.connect() as connector: response = await connector.execute( select([ sticker_counter_model.sticker_id, alchemy_function.count(sticker_counter_model.sticker_id).label('total'), ]). \ where(and_( sticker_counter_model.user_id == user.id, sticker_counter_model.timestamp > datetime.utcnow()-RELATIVE_MONTH*months, )). \ limit(count). \ group_by(sticker_counter_model.sticker_id). \ order_by(desc('total')) ) results = await response.fetchall() embed = Embed( f'Most used stickers by {user.full_name}', color=user.color_at(GUILD__NEKO_DUNGEON), ).add_thumbnail(user.avatar_url) if results: description_parts = [] limit = len(results) index = 0 start = 1 while True: sticker_id, count = results[index] index += 1 try: sticker = STICKERS[sticker_id] except KeyError: continue description_parts.append(str(index)) description_parts.append('.: **') description_parts.append(str(count)) description_parts.append('** x ') description_parts.append(sticker.name) if (not index % 10) or (index == limit): description = ''.join(description_parts) description_parts.clear() embed.add_field(f'{start} - {index}', description, inline=True) if (index == limit): break start = index + 1 continue description_parts.append('\n') continue else: embed.description = '*No recorded data.*' return embed
async def yeet( client, event, user: ('user', 'Select the user to yeet!'), reason: ('str', 'Any reason why you would want to yeet?') = None, delete_message_days: (range(8), 'Delete previous messages?') = 0, notify_user: ('bool', 'Whether the user should get DM about the ban.') = True, ): """Yeets someone out of the guild. You must have ban users permission.""" # Check 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 do this.') if not event.user_permissions.can_ban_users: abort('You must have yeet users permission to use this command.') if not guild.cached_permissions_for(client).can_ban_users: abort(f'{client.name_at(guild)} cannot yeet in the guild.') if not event.user.has_higher_role_than_at(user, guild): abort('You must have higher role than the person to be yeeted.') if not client.has_higher_role_than_at(user, guild): abort('I must have higher role than the person to yeeted.') # Ask, whether the user should be banned. if (reason is not None) and (not reason): reason = None embed = Embed('Confirmation', f'Are you sure to yeet {user.mention} from {guild.name}?'). \ add_field('Delete message day', str(delete_message_days), inline=True). \ add_field('Notify user', 'true' if notify_user else 'false', inline=True). \ add_field('Reason', '*No reason provided.*' if reason is None else reason) message = yield InteractionResponse(embed=embed, components=BAN_COMPONENTS, allowed_mentions=None) # Wait for user input try: component_interaction = await wait_for_component_interaction( message, timeout=300.0, check=partial_func(check_banner, event.user)) except TimeoutError: embed.title = 'Timeout' embed.description = f'{user.mention} was not yeeted from {guild.name}.' # Edit the source message with the source interaction yield InteractionResponse(embed=embed, components=None, allowed_mentions=None, message=message) return if component_interaction.interaction == BAN_BUTTON_CANCEL: embed.title = 'Cancelled' embed.description = f'{user.mention} was not yeeted from {guild.name}.' # 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 to notify the user. Ignore bot notifications. if notify_user: if user.is_bot: notify_note = None else: try: channel = await client.channel_private_create(user) except BaseException as err: if isinstance(err, ConnectionError): return # We cannot help no internet raise embed = Embed('Yeeted', f'You were yeeted from {guild.name}.'). \ add_field('Reason', '*No reason provided.*' if reason is None else reason) try: await client.message_create(channel, embed=embed) except BaseException as err: if isinstance(err, ConnectionError): return # We cannot help no internet elif isinstance(err, DiscordException) and ( err.code == ERROR_CODES.cannot_message_user): notify_note = 'Notification cannot be delivered: user has DM disabled.' else: raise else: notify_note = None else: notify_note = None if reason is None: caller = event.user reason = f'Requested by: {caller.full_name} [{caller.id}]' await client.guild_ban_add(guild, user, delete_message_days=delete_message_days, reason=reason) embed = Embed('Hecatia yeah!', f'{user.full_name} has been yeeted.') if (notify_note is not None): embed.add_footer(notify_note) # Edit the source message yield InteractionResponse(embed=embed, message=message, components=None)
async def runner(self): client = self.client channel = self.channel answers = self.answers buffer = ReuBytesIO() embed = Embed(color=KANAKO_COLOR).add_image( 'attachment://guess_me.png').add_footer('') time_till_notify = CIRCLE_TIME - 10 for index, (question, answer) in enumerate(self.map, 1): embed.footer.text = f'{index} / {len(self.map)}' if self.possibilities: self.options = self.generate_options(answer) embed.description = '\n'.join([ f'**{index}.: {value}**' for index, value in enumerate(self.options, 1) ]) try: await client.message_create(channel, embed=embed, file=('guess_me.png', draw(buffer, question))) except BaseException as err: self.cancel() if isinstance(err, ConnectionError): return if isinstance(err, DiscordException): if err.code in ( ERROR_CODES.unknown_channel, # channel deleted ERROR_CODES.missing_access, # client removed ERROR_CODES. missing_permissions, # permissions changed meanwhile ): return await client.events.error(client, f'{self.__class__.__name__}.runner', err) return circle_start = LOOP_TIME() self.waiter = waiter = Future(KOKORO) future_or_timeout(waiter, time_till_notify) try: await waiter except TimeoutError: Task(self.notify_late_users(), KOKORO) self.waiter = waiter = Future(KOKORO) future_or_timeout(waiter, 10) try: await waiter except TimeoutError: leavers = [] users = self.users for index in reversed(range(len(users))): user = users[index] if user in answers: continue leavers.append(user) del users[index] for element in self.history: del element.answers[index] if len(self.users) == 0: self.cancel() embed = Embed( None, 'No-one gave answer in time, cancelling the game.', color=KANAKO_COLOR) else: embed = Embed(None, '\n'.join([ 'Users timed out:', *(user.full_name for user in leavers) ]), color=KANAKO_COLOR) try: await client.message_create(channel, embed=embed) except BaseException as err: self.cancel() if isinstance(err, ConnectionError): return if isinstance(err, DiscordException): if err.code in ( ERROR_CODES. unknown_channel, # channel deleted ERROR_CODES. missing_access, # client removed ERROR_CODES. missing_permissions, # permissions changed meanwhile ): return await client.events.error( client, f'{self.__class__.__name__}.runner', err) return if self.cancelled: return self.waiter = None element = HistoryElement() element.question = question element.answer = answer element.options = self.options element.answers = [(value[0], value[1] - circle_start) for value in (answers[user.id] for user in self.users)] self.history.append(element) answers.clear() embed.title = f'Last answer: {answer}' self.cancel() embed = Embed(embed.title, color=KANAKO_COLOR) try: await client.message_create(channel, embed=embed) except BaseException as err: self.cancel() if isinstance(err, ConnectionError): return if isinstance(err, DiscordException): if err.code in ( ERROR_CODES.unknown_channel, # channel deleted ERROR_CODES.missing_access, # client removed ERROR_CODES. missing_permissions, # permissions changed meanwhile ): return await client.events.error(client, f'{self.__class__.__name__}.runner', err) return await GameStatistics(self)
async def buy(client, event, item : ([(item.name, item.id) for item in BUYABLE], 'Select the item to buy nya!'), amount : (int, 'How much items would you want to buy?'), ): """Buy?""" try: item = ITEMS[item] except KeyError: abort('Item not available.') permissions = event.channel.cached_permissions_for(client) if (not permissions.can_send_messages) or (not permissions.can_add_reactions): abort('I need `send messages` and `add reactions` permissions to execute the command.') yield user = event.user async with DB_ENGINE.connect() as connector: response = await connector.execute( select([currency_model.total_love]). \ where(currency_model.user_id==user.id)) results = await response.fetchall() if results: total_love = results[0] else: total_love = 0 embed = Embed('Confirm buying', f'Selected item: {item.emoji:e} **{item.name}**\n' f'Amount: **{amount}**\n' f'\n' f'Price: {calculate_buy_cost(item.market_cost, amount)} {EMOJI__HEART_CURRENCY:e}\n' f'Budget: {total_love} {EMOJI__HEART_CURRENCY:e}' ) embed.add_author(user.avaar_url, user.full_name) embed.add_footer('The prices of context of demand and supply.') message = await client.message_create(event.channel, embed=embed) await client.reaction_add(message, item.emoji) await client.reaction_add(message, CONFIRM_NAH) try: event = await wait_for_reaction(client, message, partial_func(check_confirm_emoji, item.emoji), 300.0) except TimeoutError: return if event.emoji is CONFIRM_NAH: embed.title = 'Buying cancelled' else: user = event.user async with DB_ENGINE.connect() as connector: response = await connector.execute( select([currency_model.total_love, currency_model.total_allocated]). \ where(currency_model.user_id==user.id)) results = await response.fetchall() if results: total_love, total_allocated = results[0] else: total_love = total_allocated = 0 if total_love == 0: amount = cost = 0 else: amount, cost = calculate_buyable_and_cost(item.market_cost, amount, total_love-total_allocated) item.market_cost += amount if cost == 0: new_love = total_love else: new_love = total_love-cost await connector.execute(update(currency_model.user_id==user.id). \ values(total_love = new_love)) response = await connector.execute(select([item_model.id, item_model.amount]). \ where(item_model.user_id==user.id).where(item_model.type==item.id)) results = await response.fetchall() if results: row_id, actual_amount = results[0] new_amount = actual_amount+amount to_execute = ITEM_TABLE.update().values( amount=new_amount ).where(item_model.id==row_id) else: to_execute = ITEM_TABLE.insert().values( user_id = user.id, amount = amount, type = item.id ) await connector.execute(to_execute) embed.title = 'Buying confirmed' embed.description = ( f'Selected item: {item.emoji:e} **{item.name}**\n' f'Bought mount: **{amount}**\n' f'\n' f'Hearts: {total_love} {EMOJI__HEART_CURRENCY:e} -> {new_love} {EMOJI__HEART_CURRENCY:e}' ) await client.message_edit(message, embed=embed)
async def handle_waifu_select(client, event): # We filter out 3rd party users based on original and current invoking user. if event.message.interaction.user is not event.user: return # Second we filter out incorrect selected values. # You can change the command over time and the can return bad option as well. selected_waifu_types = event.interaction.options if (selected_waifu_types is None): return selected_waifu_type = selected_waifu_types[0] if (selected_waifu_type not in WAIFU_TYPES): return # Try to get url from cache cache = WAIFU_CACHE_BY_KEY[selected_waifu_type] if cache: url = cache.pop() else: # We could not get url from cache # Do 1 yield to acknowledge the event. yield # We could use a Lock to avoid parallel requests, but that would expose us to other edge cases. async with client.http.post( f'{WAIFU_API_BASE_URL}/many/sfw/{selected_waifu_type}', headers=WAIFU_API_HEADERS, data=WAIFU_API_REQUEST_DATA, ) as response: if response.status == 200: data = await response.json() else: data = None url = None if (data is not None): try: files = data['files'] except KeyError: pass else: cache.extend(files) if cache: url = cache.pop() # Url defaults to `None`, so passing it to `url` field is fine. embed = Embed('Please select a waifu type to ship.', url=url) if url is None: embed.description = ( f'*Could not find any free {selected_waifu_type} now.\n' f'Please try again later.*') else: embed.add_image(url) # We re-build the select again with one difference, we mark the used one as default. select = Select( [ Option(waifu_type, waifu_type, default=(waifu_type == selected_waifu_type)) for waifu_type in WAIFU_TYPES ], custom_id=WAIFU_CUSTOM_ID, ) yield InteractionResponse(embed=embed, components=select)
async def run(self): client = self.client channel = self.channel answers = self.answers buffer = ReuBytesIO() embed = Embed(color=KANAKO_COLOR) embed.add_image('attachment://guessme.png') embed.add_footer('') time_till_notify = CIRCLE_TIME - 10 for index, (question, answer) in enumerate(self.map, 1): embed.footer.text = f'{index} / {len(self.map)}' if self.possibilities: self.options = self.generate_options(answer) embed.description = '\n'.join([ f'**{index}.: {value}**' for index, value in enumerate(self.options, 1) ]) try: await client.message_create(channel, embed=embed, file=('guessme.png', draw(buffer, question))) except DiscordException: return self.cancel() circle_start = LOOP_TIME() waiter = sleep(time_till_notify, client.loop) self.waiter = waiter try: await waiter Task( self.send_or_except( Embed( 'Hurry! Only 10 seconds left!', '\n'.join([ user.full_name for user in self.users if user.id not in answers ]), KANAKO_COLOR)), client.loop) waiter = sleep(time_till_notify, client.loop) self.waiter = waiter await waiter self.calculate_leavers() except CancelledError: pass except InterruptedError as err: return self.cancel() element = history_element() element.question = question element.answer = answer element.options = self.options element.answers = [(value[0], value[1] - circle_start) for value in (answers[user.id] for user in self.users)] self.history.append(element) answers.clear() embed.title = f'Last answer: {answer}' await self.send_or_except(Embed(embed.title, '', KANAKO_COLOR)) del ACTIVE_GAMES[channel.id] client.events.message_create.remove(self.channel, self) self.running = False await game_statistics(self)