async def roles(self, ctx): relevant_roles = list( filter(lambda role: role.name.startswith('🎨'), ctx.guild.roles)) roles_counter = Counter( (role for member in ctx.guild.members for role in member.roles)) sorted_roles = sorted( relevant_roles, key=lambda role: colorsys.rgb_to_hsv(*role.color.to_rgb())) if relevant_roles: role_parts = [ f'{role.mention} – `{str(role.color).upper()}` – 👥 {roles_counter[role]}' for role in sorted_roles ] random_role_index = random.randint(0, len(relevant_roles) - 1) role_parts[random_role_index] += ' ←' available_form = word_number_form(len(role_parts), 'Dostępny', 'Dostępne', include_number=False) color_role_form = word_number_form(len(role_parts), 'kolor–rola', 'kolory–role', 'kolorów–ról') emoji, notice = '🎨', f'{available_form} {color_role_form}' description = '\n'.join(role_parts) color = sorted_roles[random_role_index].color else: emoji, notice = '❔', 'Brak kolorów–ról' description = None color = self.GRAY embed = self.bot.generate_embed(emoji, notice, description, color=color) await self.bot.send(ctx, embed=embed)
def _get_birthday_public_servers_presentation( self, born_person: BornPerson, *, on_server_id: Optional[int] = None, period: bool = True) -> str: if born_person.birthday is None: return f'Nie ustawiłeś swojej daty urodzin, więc nie ma czego upubliczniać{"." if period else ""}', None extra = None if not born_person.birthday_public_servers: info = 'nigdzie' else: number_of_other_servers = len(born_person.birthday_public_servers) if on_server_id: public_here = any( (server.id == on_server_id for server in born_person.birthday_public_servers)) if public_here: number_of_other_servers -= 1 if number_of_other_servers == 0: info = 'tylko tutaj' else: other_servers_number_form = word_number_form( number_of_other_servers, 'innym serwerze', 'innych serwerach') if public_here: info = f'tutaj i na {other_servers_number_form}' else: info = f'na {other_servers_number_form}' these_servers_number_form = word_number_form( number_of_other_servers, 'Nazwę tego serwera', 'Nazwy tych serwerów', include_number=False) extra = f'{these_servers_number_form} otrzymasz używającej tej komendy w wiadomościach prywatnych.' else: names = [ self.bot.get_guild(server.id).name for server in born_person.birthday_public_servers ] servers_number_form = word_number_form(number_of_other_servers, 'serwerze', 'serwerach', include_number=False) names_before_and = ', '.join(names[:-1]) name_after_and = names[-1] names_presentation = ' oraz '.join( filter(None, (names_before_and, name_after_and))) info = f'na {servers_number_form} {names_presentation}' main = f'Twoja data urodzin jest w tym momencie publiczna {info}{"." if period else ""}' return main, extra
async def how_much_longer(self, ctx): """Says how much of the school year or summer break is left.""" current_school_period = SchoolPeriod() days_left_as_weeks = days_as_weeks(current_school_period.days_left) left_form = word_number_form(current_school_period.days_left, 'został', 'zostały', 'zostało', include_number=False) day_form = word_number_form(current_school_period.days_left, 'dzień', 'dni') if not current_school_period.is_summer_break: if current_school_period.days_left == 0: embed = self.bot.generate_embed( '🎉', 'Dziś zakończenie roku szkolnego') elif current_school_period.days_passed == 0: embed = self.bot.generate_embed( '⛓', 'Dziś rozpoczęcie roku szkolnego') else: embed = self.bot.generate_embed( '📚', f'Do końca roku szkolnego {left_form} {day_form}', f'To {days_left_as_weeks}.' if days_left_as_weeks is not None else None, ) embed.add_field( name='Postęp', value= f'{round(current_school_period.fraction_passed * 100, 1):n}%' ) else: embed = self.bot.generate_embed( '🏖', f'Do końca wakacji {left_form} {day_form}', f'To {days_left_as_weeks}.' if days_left_as_weeks is not None else None, ) embed.add_field( name='Postęp', value= f'{round(current_school_period.fraction_passed * 100, 1):n}%') embed.add_field( name='Data rozpoczęcia', value=current_school_period.start_date.strftime('%-d %B %Y')) embed.add_field( name='Data zakończenia', value=current_school_period.end_date.strftime('%-d %B %Y')) await self.bot.send(ctx, embed=embed)
async def _finalize_progress(self): if self.caching_progress_message is not None: new_messages_form = word_number_form( self.messages_cached, 'nową wiadomość', 'nowe wiadomości', 'nowych wiadomości' ) caching_progress_embed = self.ctx.bot.generate_embed('✅', f'Zbuforowano {new_messages_form}') await self.caching_progress_message.edit(embed=caching_progress_embed)
async def purge(self, ctx, number_of_messages_to_delete: int = 1): """Removes last number_of_messages_to_delete messages from the channel.""" number_of_messages_to_delete = min(number_of_messages_to_delete, 100) await ctx.channel.purge(limit=number_of_messages_to_delete + 1) last_adjective_variant = word_number_form(number_of_messages_to_delete, 'ostatnią', 'ostatnie', 'ostatnich') messages_noun_variant = word_number_form(number_of_messages_to_delete, 'wiadomość', 'wiadomości', include_number=False) embed = self.bot.generate_embed( '✅', f'Usunięto z kanału {last_adjective_variant} {messages_noun_variant}' ) await self.bot.send(ctx, embed=embed)
async def pardon(self, ctx, subject_user: discord.Member): """Clears specified member's warnings.""" with data.session(commit=True) as session: warning_deleted_count = (session.query(Event).filter( Event.server_id == ctx.guild.id, Event.user_id == subject_user.id, Event.type == 'warned').delete()) if warning_deleted_count: warning_form = word_number_form(warning_deleted_count, 'ostrzeżenie', 'ostrzeżenia', 'ostrzeżeń') emoji = '✅' notice = f'Usunięto {warning_form} {subject_user}' event = Event( type='pardoned', server_id=ctx.guild.id, channel_id=ctx.channel.id, user_id=subject_user.id, executing_user_id=ctx.author.id, details=warning_form, ) session.add(event) else: emoji = 'ℹ️' notice = f'{subject_user} nie ma na ostrzeżeń do usunięcia' await self.bot.send(ctx, embed=self.bot.generate_embed(emoji, notice))
def test_plural_noun_12_to_14(self): for number in ( sign * (base + addition) for base in self._BASES for addition in list(range(12, 15)) for sign in self._SIGNS ): with self.subTest(number=number): returned_variant_with_number = word_number_form(number, 'klocek', 'klocki', 'klocków') expected_variant_with_number = f'{number:n} klocków' self.assertEqual(returned_variant_with_number, expected_variant_with_number)
def test_fractional_noun(self): for number in ( sign * (base + addition + 0.5) for base in self._BASES for addition in range(100) for sign in self._SIGNS ): with self.subTest(number=number): returned_variant_with_number = word_number_form(number, 'klocek', 'klocki', 'klocków', 'klocka') expected_variant_with_number = f'{number:n} klocka' self.assertEqual(returned_variant_with_number, expected_variant_with_number)
async def file(self, ctx, member: Union[discord.Member, int] = None, *, event_types: Event.comprehend_types = None): """Responds with a list of the user's files events on the server.""" if isinstance(member, int): search_by_non_member_id = True member_id = member try: member = await self.bot.fetch_user(member) except discord.NotFound: member = None else: search_by_non_member_id = False member = member or ctx.author member_id = member.id with data.session() as session: events = session.query(Event) if event_types is None: events = events.filter(Event.server_id == ctx.guild.id, Event.user_id == member_id) else: events = events.filter(Event.server_id == ctx.guild.id, Event.user_id == member_id, Event.type.in_(event_types)) events = events.order_by(Event.occurred_at).all() if member == ctx.author: address = 'Twoja kartoteka' else: address = f'Kartoteka {member if member else "usuniętego użytkownika"}' if events: if event_types is None: event_types_description = '' elif len(event_types) == 1: event_types_description = ' podanego typu' elif len(event_types) > 1: event_types_description = ' podanych typów' event_number_form = word_number_form(len(events), 'zdarzenie', 'zdarzenia', 'zdarzeń') embed = self.bot.generate_embed( '📂', f'{address} zawiera {event_number_form}{event_types_description}', 'Pokazuję 25 najnowszych.' if len(events) > 25 else '') for event in events[-25:]: embed.add_field( name=await event.get_presentation(self.bot), value=text_snippet(event.details, Event.MAX_DETAILS_LENGTH) if event.details is not None else '—', inline=False) else: if search_by_non_member_id: embed = self.bot.generate_embed( '⚠️', 'Nie znaleziono na serwerze pasującego użytkownika') else: notice = 'jest pusta' if event_types is None else 'nie zawiera zdarzeń podanego typu' embed = self.bot.generate_embed('📂', f'{address} {notice}') await self.bot.send(ctx, embed=embed)
async def vote_error(self, ctx, error): notice = None if isinstance(error, commands.MissingRequiredArgument): notice = 'Nie podano sprawy w jakiej ma się odbyć głosowanie' elif isinstance(error, commands.BadArgument): character_form = word_number_form(Ballot.MAX_MATTER_LENGTH, 'znak', 'znaki', 'znaków') notice = f'Tekstu sprawy nie może być dłuższy niż {character_form}' if notice is not None: embed = self.bot.generate_embed('⚠️', notice) await self.bot.send(ctx, embed=embed)
def test_plural_verb(self): for number in ( sign * (base + addition) for base in self._BASES for addition in list(range(2, 100)) + [0] for sign in self._SIGNS ): with self.subTest(number=number): returned_variant_with_number = word_number_form(number, 'skoczył', 'skoczyło') expected_variant_with_number = f'{number:n} skoczyło' self.assertEqual(returned_variant_with_number, expected_variant_with_number)
async def check(self, ctx): """Presents the current command prefix.""" if not ctx.guild: extra_prefixes = ('', configuration['command_prefix']) extra_prefixes_presentation = f'brak lub domyślna `{configuration["command_prefix"]}`' notice = 'W wiadomościach prywatnych prefiks jest zbędny (choć obowiązuje też domyślny)' elif not self.bot.prefixes.get(ctx.guild.id): extra_prefixes = (configuration['command_prefix'], ) extra_prefixes_presentation = f'domyślna `{configuration["command_prefix"]}`' notice = 'Obowiązuje domyślny prefiks' else: extra_prefixes = self.bot.prefixes[ctx.guild.id] applies_form = word_number_form(len(extra_prefixes), 'Obowiązuje', 'Obowiązują', 'Obowiązuje', include_number=False) prefix_form = word_number_form( len(extra_prefixes), 'własny prefiks serwerowy', 'własne prefiksy serwerowe', 'własnych prefiksów serwerowych', ) notice = f'{applies_form} {prefix_form}' extra_prefixes_presentation = ' lub '.join( (f'`{prefix}`' for prefix in reversed(extra_prefixes))) embed = self.bot.generate_embed('🔧', notice) embed.add_field( name='Wartość' if len(extra_prefixes) == 1 else 'Wartości', value=extra_prefixes_presentation, inline=False) embed.add_field( name='Przykłady wywołań', value= (f'`{random.choice(extra_prefixes)}wersja` lub `{random.choice(extra_prefixes)} oof` lub `{ctx.me} urodziny`' if ctx.guild else f'`wersja` lub `{configuration["command_prefix"]} oof` lub `{ctx.me} urodziny`' ), inline=False, ) await self.bot.send(ctx, embed=embed)
def _embed_analysis_metastats(self): """Adds information about analysis time as the report embed's footer.""" completion_timedelta = dt.datetime.now() - self.init_datetime completion_seconds = round(completion_timedelta.total_seconds(), 2) new_messages_form = word_number_form(self.messages_cached, "nowej wiadomości", "nowych wiadomości") if not self.initiated_queue_processing: queue_timedelta = self.out_of_queue_datetime - self.init_datetime queue_seconds = round(queue_timedelta.total_seconds(), 2) footer_text = ( f'Wygenerowano w {completion_seconds:n} s (z czego {queue_seconds:n} s w serwerowej kolejce analizy) ' f'buforując metadane {new_messages_form}' ) else: footer_text = ( f'Wygenerowano w {completion_seconds:n} s buforując metadane {new_messages_form}' ) self.embed.set_footer(text=footer_text)
def generate_tv_embed(self, result: dict) -> discord.Embed: first_air_date = dt.date.fromisoformat(result['first_air_date']) if result['first_air_date'] else None last_air_date = dt.date.fromisoformat(result['last_air_date']) if result['last_air_date'] else None air_years_range = str(first_air_date.year) if result['in_production']: air_years_range += '–' elif first_air_date.year != last_air_date.year: air_years_range += f'–{last_air_date.year}' embed = self.bot.generate_embed( '📺', f'{result["name"]} ({air_years_range})', url=f'https://www.themoviedb.org/tv/{result["id"]}' ) if result.get('original_name') != result['name']: embed.add_field(name='Tytuł oryginalny', value=result['original_name'], inline=False) embed.add_field(name='Średnia ocen', value=f'{result["vote_average"]:n} / 10') embed.add_field(name='Głosów', value=f'{result["vote_count"]:n}') if first_air_date is not None: embed.add_field( name='Data premiery pierwszego odcinka', value=first_air_date.strftime('%-d %B %Y'), inline=False ) if last_air_date is not None and last_air_date != first_air_date: embed.add_field( name=f'Data premiery ostatniego odcinka', value=last_air_date.strftime('%-d %B %Y'), inline=False ) if result['networks']: network_parts = ( f'[{network["name"]}](https://www.themoviedb.org/network/{network["id"]})' for network in result['networks'] ) embed.add_field(name='Sieci' if len(result['networks']) > 1 else 'Sieć', value=', '.join((network_parts))) if result['created_by']: author_parts = ( f'[{author["name"]}](https://www.themoviedb.org/person/{author["id"]})' for author in result['created_by'] ) if len(result['created_by']) > 1: are_all_authors_female = all((author.get('gender') == 1 for author in result['created_by'])) created_by_field_name = 'Autorki' if are_all_authors_female else 'Autorzy' else: created_by_field_name = 'Autorka' if result['created_by'][0].get('gender') == 1 else 'Autor' embed.add_field(name=created_by_field_name, value=', '.join(author_parts)) if result['genres']: genre_parts = ( f'[{genre["name"]}](https://www.themoviedb.org/genre/{genre["id"]})' for genre in result['genres'] ) embed.add_field(name='Gatunki' if len(result['genres']) > 1 else 'Gatunek', value=' / '.join((genre_parts))) season_parts = [] season_counter = 0 for season in result['seasons']: if season['season_number'] < 1: continue season_counter += 1 if season['season_number'] > 10: continue if season['air_date']: air_date_presentation = dt.date.fromisoformat(season['air_date']).strftime('%-d %B %Y') else: air_date_presentation = 'TBD' season_parts.append( f'[{season["season_number"]}. ' f'{word_number_form(season["episode_count"] or "?", "odcinek", "odcinki", "odcinków")} ' f'(premiera {air_date_presentation})]' f'(https://www.themoviedb.org/tv/{result["id"]}/season/{season["season_number"]})' ) if season_counter > 10: following_form = word_number_form(season_counter - 10, 'kolejny', 'kolejne', 'kolejnych') season_parts.append( f'[…i jeszcze {following_form}](https://www.themoviedb.org/tv/{result["id"]}/seasons)' ) embed.add_field(name='Sezony', value='\n'.join(season_parts), inline=False) if result['overview']: embed.add_field(name='Opis', value=text_snippet(result['overview'], 500), inline=False) if result.get('poster_path'): embed.set_thumbnail(url=f'https://image.tmdb.org/t/p/w342{result["poster_path"]}') if result.get('backdrop_path'): embed.set_image(url=f'https://image.tmdb.org/t/p/w780{result["backdrop_path"]}') return embed
async def file(self, ctx, member: Union[discord.Member, int] = None, *, event_types: Event.comprehend_types = None): """Responds with a list of the user's files events on the server.""" if isinstance(member, int): search_by_non_member_id = True member_id = member try: member = await self.bot.fetch_user(member) except discord.NotFound: member = None else: search_by_non_member_id = False member = member or ctx.author member_id = member.id with data.session() as session: events = session.query(Event) if event_types is None: events = events.filter(Event.server_id == ctx.guild.id, Event.user_id == member_id) else: events = events.filter(Event.server_id == ctx.guild.id, Event.user_id == member_id, Event.type.in_(event_types)) events = events.order_by(Event.occurred_at.desc()).all() if member == ctx.author: address = 'Twoja kartoteka' else: address = f'Kartoteka {member if member else "usuniętego użytkownika"}' if events: current_page_index = 0 page_count = ceil(len(events) / self.PAGE_FIELDS) event_types_description = '' if event_types is not None: if len(event_types) == 1: event_types_description = ' podanego typu' elif len(event_types) > 1: event_types_description = ' podanych typów' event_number_form = word_number_form(len(events), 'zdarzenie', 'zdarzenia', 'zdarzeń') async def generate_events_embed() -> discord.Embed: embed = self.bot.generate_embed( '📂', f'{address} zawiera {event_number_form}{event_types_description}', ) relevant_events = events[self.PAGE_FIELDS * current_page_index:self.PAGE_FIELDS * (current_page_index + 1)] if page_count > 1: embed.description = ( f"Strona {current_page_index+1}. z {page_count}. Do {self.PAGE_FIELDS} zdarzeń na stronę." ) for event_offset, event in enumerate( relevant_events, self.PAGE_FIELDS * current_page_index): embed.add_field( name= f"{len(events)-event_offset}. {await event.get_presentation(self.bot)}", value=text_snippet(event.details, Event.MAX_DETAILS_LENGTH) if event.details is not None else '—', inline=False, ) return embed file_message = cast( discord.Message, await self.bot.send(ctx, embed=await generate_events_embed())) relevant_emojis = ['👈', '👉'] await file_message.add_reaction('👈') if page_count > 2: for specific_page_emoji in SPECIFIC_PAGE_EMOJIS[:page_count]: await file_message.add_reaction(specific_page_emoji) relevant_emojis.append(specific_page_emoji) await file_message.add_reaction('👉') while True: reaction, user = await self.bot.wait_for( 'reaction_add', check=lambda reaction, user: not user.bot and str( reaction.emoji) in relevant_emojis) reaction_emoji = str(reaction.emoji) try: await cast(discord.Reaction, reaction).remove(user) except: pass if reaction_emoji == '👈': if current_page_index > 0: current_page_index -= 1 elif reaction_emoji == '👉': if current_page_index < page_count - 1: current_page_index += 1 else: current_page_index = SPECIFIC_PAGE_EMOJIS.index( reaction_emoji) await file_message.edit(embed=await generate_events_embed()) else: if search_by_non_member_id: embed = self.bot.generate_embed( '⚠️', 'Nie znaleziono na serwerze pasującego użytkownika') else: notice = 'jest pusta' if event_types is None else 'nie zawiera zdarzeń podanego typu' embed = self.bot.generate_embed('📂', f'{address} {notice}') await self.bot.send(ctx, embed=embed)
def test_singular_verb(self): returned_variant_with_number = word_number_form(1, 'skoczył', 'skoczyło') expected_variant_with_number = '1 skoczył' self.assertEqual(returned_variant_with_number, expected_variant_with_number)
async def robot9000(self, ctx: commands.Context, sent_by: discord.Member = None): """Finds previous occurences of the image being sent.""" with ctx.typing(): attachment, _ = await self.find_image( ctx.channel, sent_by=sent_by, message_id=ctx.message.reference.message_id if ctx.message is not None and ctx.message.reference is not None else None, ) if attachment is not None: with data.session() as session: similar = [] base_image9000 = session.query(Image9000).get( attachment.id) if base_image9000 is None: embed = self.bot.generate_embed( '⚠️', 'Nie znaleziono obrazka do sprawdzenia') else: init_time = time.time() comparison_count = 0 sent_by = ctx.guild.get_member(base_image9000.user_id) for other_image9000 in session.query(Image9000).filter( Image9000.server_id == ctx.guild.id, Image9000.attachment_id != attachment.id): comparison_count += 1 similarity = base_image9000.calculate_similarity_to( other_image9000) if similarity >= self.IMAGE9000_SIMILARITY_TRESHOLD: similar.append((other_image9000, similarity)) if similar: embed = self.bot.generate_embed() for image9000, similarity in similar: channel = image9000.discord_channel(self.bot) message = None info = '' if channel is not None: try: message = await channel.fetch_message( image9000.message_id) except discord.NotFound: info = ' (wiadomość usunięta)' else: info = ' (kanał usunięty)' similarity_presentantion = f'{int(round(similarity*100))}% podobieństwa' embed.add_field( name=await image9000.get_presentation(self.bot), value=md_link( similarity_presentantion, message.jump_url if message is not None else None) + info, inline=False, ) occurences_form = word_number_form( len(embed.fields), 'wcześniejsze wystąpienie', 'wcześniejsze wystąpienia', 'wcześniejszych wystąpień', ) embed.title = ( f'🤖 Wykryłem {occurences_form} na serwerze obrazka wysłanego przez {sent_by} o ' f'{base_image9000.sent_at.strftime("%-H:%M")}') else: embed = self.bot.generate_embed( '🤖', f'Nie wykryłem, aby obrazek wysłany przez {sent_by} ' f'o {base_image9000.sent_at.strftime("%-H:%M")} wystąpił wcześniej na serwerze', ) comparison_time = time.time() - init_time seen_image_form = word_number_form( comparison_count, 'obrazek zobaczony', 'obrazki zobaczone', 'obrazków zobaczonych') embed.set_footer( text= f'Przejrzano {seen_image_form} do tej pory na serwerze w {round(comparison_time, 2):n} s.' ) else: embed = self.bot.generate_embed( '⚠️', 'Nie znaleziono obrazka do sprawdzenia') await self.bot.send(ctx, embed=embed)
def test_plural_noun_with_with(self): returned_variant_without_number = word_number_form(106, 'klockiem', 'klockami', include_with=True) expected_variant_without_number = 'ze 106 klockami' self.assertEqual(returned_variant_without_number, expected_variant_without_number)
def test_singular_noun_with_with(self): returned_variant_without_number = word_number_form(1, 'klockiem', 'klockami', include_with=True) expected_variant_without_number = 'z 1 klockiem' self.assertEqual(returned_variant_without_number, expected_variant_without_number)
def test_singular_noun_without_number(self): returned_variant_without_number = word_number_form(1, 'klocek', 'klocki', 'klocków', include_number=False) expected_variant_without_number = 'klocek' self.assertEqual(returned_variant_without_number, expected_variant_without_number)
def test_singular_noun(self): returned_variant_with_number = word_number_form(1, 'klocek', 'klocki', 'klocków') expected_variant_with_number = '1 klocek' self.assertEqual(returned_variant_with_number, expected_variant_with_number)