예제 #1
0
파일: disco.py 프로젝트: Twixes/somsiad
class Disco(commands.Cog):
    GROUP = Help.Command(('disco', 'd'), (), 'Komendy związane z odtwarzaniem muzyki.')
    COMMANDS = (
        Help.Command(('zagraj', 'graj'), 'zapytanie/link', 'Odtwarza utwór na kanale głosowym.'),
        Help.Command(
            ('powtórz', 'powtorz', 'replay'),
            (),
            'Odtwarza od początku obecnie lub ostatnio odtwarzany na serwerze utwór.',
        ),
        Help.Command(('spauzuj', 'pauzuj', 'pauza'), (), 'Pauzuje obecnie odtwarzany utwór.'),
        Help.Command(('wznów', 'wznow'), (), 'Wznawia odtwarzanie utworu.'),
        Help.Command(('pomiń', 'pomin'), (), 'Pomija obecnie odtwarzany utwór.'),
        Help.Command(
            ('głośność', 'glosnosc', 'volume', 'vol'),
            '?nowa głośność w procentach',
            'Sprawdza głośność odtwarzania lub, jeśli podano <?nową głośność>, ustawia ją.',
        ),
        Help.Command(('rozłącz', 'rozlacz', 'stop'), (), 'Rozłącza z kanału głosowego.'),
    )
    HELP = Help(COMMANDS, '🔈', group=GROUP)

    def __init__(self, bot: Somsiad):
        self.cache_dir_path = os.path.join(bot.cache_dir_path, 'disco')
        self.bot = bot
        self.servers = defaultdict(lambda: {'volume': 1.0, 'song_url': None, 'song_audio': None})
        if not os.path.exists(self.cache_dir_path):
            os.makedirs(self.cache_dir_path)

    @staticmethod
    async def channel_connect(channel: discord.VoiceChannel):
        for _ in range(3):
            try:
                if channel.guild.voice_client is None:
                    await channel.connect()
                elif channel.guild.voice_client.channel != channel:
                    await channel.guild.voice_client.move_to(channel)
            except asyncio.futures.TimeoutError:
                continue
            else:
                break

    @staticmethod
    async def server_disconnect(server: discord.Guild) -> Optional[discord.VoiceChannel]:
        if server.me.voice is None:
            return None
        channel = server.me.voice.channel
        for _ in range(3):
            try:
                await server.voice_client.disconnect()
            except asyncio.futures.TimeoutError:
                continue
            else:
                break
        return channel

    async def channel_play_song(self, ctx: commands.Context, query: str):
        channel = ctx.author.voice.channel
        await self.channel_connect(channel)
        try:
            pytube.extract.video_id(query)
        except pytube.exceptions.RegexMatchError:
            try:
                search_result = await self.bot.youtube_client.search(query)
            except (AttributeError, HTTPError):
                video_url = None
            else:
                video_url = search_result.url if search_result is not None else None
        else:
            video_url = query
        if video_url is not None:
            video_id = pytube.extract.video_id(video_url)
            try:
                video = await self.bot.loop.run_in_executor(None, pytube.YouTube, video_url)
            except:
                embed = self.bot.generate_embed('⚠️', 'Nie można zagrać tego utworu')
                await self.bot.send(ctx, embed=embed)
            else:
                embed = self.generate_embed(channel, video, 'Pobieranie', '⏳')
                message = await self.bot.send(ctx, embed=embed)
                streams = video.streams.filter(only_audio=True).order_by('abr').desc()
                stream = streams[0]
                i = 0
                while stream.filesize > configuration['disco_max_file_size_in_mib'] * 1_048_576:
                    i += 1
                    try:
                        stream = streams[i]
                    except IndexError:
                        embed = self.generate_embed(channel, video, 'Plik zbyt duży', '⚠️')
                        break
                else:
                    path = os.path.join(self.cache_dir_path, f'{video_id} - {stream.default_filename}')
                    if not os.path.isfile(path):
                        await self.bot.loop.run_in_executor(
                            None,
                            functools.partial(
                                stream.download, output_path=self.cache_dir_path, filename_prefix=f'{video_id} - '
                            ),
                        )
                    if channel.guild.voice_client is not None:
                        channel.guild.voice_client.stop()
                    song_audio = discord.PCMVolumeTransformer(
                        discord.FFmpegPCMAudio(path), self.servers[channel.guild.id]['volume']
                    )
                    self.servers[channel.guild.id]['song_audio'] = song_audio
                    self.servers[channel.guild.id]['song_url'] = video_url

                    async def try_edit(embed: discord.Embed):
                        try:
                            await message.edit(embed=embed)
                        except (discord.Forbidden, discord.NotFound):
                            pass

                    def after(error):
                        song_audio.cleanup()
                        embed = self.generate_embed(channel, video, 'Zakończono', '⏹')
                        self.bot.loop.create_task(try_edit(embed))

                    embed = self.generate_embed(channel, video, 'Odtwarzanie', '▶️')
                    await self.channel_connect(channel)
                    channel.guild.voice_client.play(self.servers[channel.guild.id]['song_audio'], after=after)
                await message.edit(embed=embed)
        else:
            embed = self.bot.generate_embed('🙁', f'Brak wyników dla zapytania "{query}"')
            await self.bot.send(ctx, embed=embed)

    def server_change_volume(self, server: discord.Guild, volume_percentage: Number):
        volume_float = abs(float(volume_percentage)) / 100
        self.servers[server.id]['volume'] = volume_float
        if self.servers[server.id]['song_audio']:
            self.servers[server.id]['song_audio'].volume = volume_float

    def generate_embed(
        self, channel: discord.VoiceChannel, video: pytube.YouTube, status: str, emoji: str, notice: str = None
    ) -> discord.Embed:
        try:
            title = video.title
            watch_url = video.watch_url
        except HTTPError:
            embed = self.bot.generate_embed('⚠️', 'Materiał niedostępny')
        else:
            embed = self.bot.generate_embed(emoji, f'{title} – {notice}' if notice else title, url=watch_url)
            embed.set_author(name=video.author)
            embed.set_thumbnail(url=video.thumbnail_url)
            embed.add_field(name='Długość', value=human_amount_of_time(int(video.length)))
            embed.add_field(name='Kanał', value=channel.name)
            embed.add_field(name='Głośność', value=f'{int(self.servers[channel.guild.id]["volume"] * 100)}%')
            embed.add_field(name='Status', value=status)
            embed.set_footer(icon_url=self.bot.youtube_client.FOOTER_ICON_URL, text=self.bot.youtube_client.FOOTER_TEXT)
        return embed

    @commands.group(aliases=['d'], invoke_without_command=True)
    @cooldown()
    @commands.guild_only()
    async def disco(self, ctx):
        await self.bot.send(ctx, embed=self.HELP.embeds)

    @disco.command(aliases=['play', 'zagraj', 'graj', 'puść', 'pusc', 'odtwórz', 'odtworz'])
    @cooldown()
    @commands.guild_only()
    async def disco_play(self, ctx, *, query):
        """Starts playing music on the voice channel where the invoking user currently resides."""
        if ctx.author.voice is None:
            embed = discord.Embed(
                title=':warning: Nie odtworzono utworu, bo nie jesteś połączony z żadnym kanałem głosowym!',
                color=self.bot.COLOR,
            )
            await self.bot.send(ctx, embed=embed)
        else:
            async with ctx.typing():
                await self.channel_play_song(ctx, query)

    @disco_play.error
    async def disco_play_error(self, ctx, error):
        if isinstance(error, commands.MissingRequiredArgument):
            embed = discord.Embed(title=f':warning: Nie podano zapytania ani linku!', color=self.bot.COLOR)
            await self.bot.send(ctx, embed=embed)

    @disco.command(aliases=['powtórz', 'powtorz', 'znów', 'znow', 'znowu', 'again', 'repeat', 'replay'])
    @cooldown()
    @commands.guild_only()
    async def disco_again(self, ctx):
        """Starts playing music on the voice channel where the invoking user currently resides."""
        if ctx.author.voice is None:
            embed = discord.Embed(
                title=':warning: Nie powtórzono utworu, bo nie jesteś połączony z żadnym kanałem głosowym!',
                color=self.bot.COLOR,
            )
            await self.bot.send(ctx, embed=embed)
        elif self.servers[ctx.guild.id]['song_url'] is None:
            embed = discord.Embed(
                title=':red_circle: Nie powtórzono utworu, bo nie ma żadnego do powtórzenia', color=self.bot.COLOR
            )
            await self.bot.send(ctx, embed=embed)
        else:
            async with ctx.typing():
                await self.channel_play_song(ctx, self.servers[ctx.guild.id]['song_url'])

    @disco.command(aliases=['pauza', 'spauzuj', 'pauzuj', 'pause'])
    @cooldown()
    @commands.guild_only()
    async def disco_pause(self, ctx):
        """Pauses the currently played song."""
        if ctx.voice_client is None:
            embed = discord.Embed(
                title=':red_circle: Nie spauzowano utworu, bo bot nie jest połączony z żadnym kanałem głosowym',
                color=self.bot.COLOR,
            )
        elif ctx.author.voice is None or (ctx.me.voice.channel and ctx.author.voice.channel != ctx.me.voice.channel):
            embed = discord.Embed(
                title=':warning: Odtwarzanie można kontrolować tylko będąc na tym samym kanale co bot!',
                color=self.bot.COLOR,
            )
        elif ctx.voice_client.is_paused():
            embed = discord.Embed(
                title=':red_circle: Nie spauzowano utworu, bo już jest spauzowany', color=self.bot.COLOR
            )
        elif not ctx.voice_client.is_playing():
            embed = discord.Embed(
                title=':red_circle: Nie spauzowano utworu, bo żaden nie jest odtwarzany', color=self.bot.COLOR
            )
        else:
            ctx.voice_client.pause()
            embed = discord.Embed(title=f':pause_button: Spauzowano utwór', color=self.bot.COLOR)
        await self.bot.send(ctx, embed=embed)

    @disco.command(aliases=['wznów', 'wznow', 'odpauzuj', 'unpause', 'resume'])
    @cooldown()
    @commands.guild_only()
    async def disco_resume(self, ctx):
        """Resumes playing song."""
        if ctx.voice_client is None:
            embed = discord.Embed(
                title=':red_circle: Nie wznowiono odtwarzania utworu, bo bot nie jest połączony z żadnym kanałem głosowym',
                color=self.bot.COLOR,
            )
        elif ctx.author.voice is None or (ctx.me.voice.channel and ctx.author.voice.channel != ctx.me.voice.channel):
            embed = discord.Embed(
                title=':warning: Odtwarzanie można kontrolować tylko będąc na tym samym kanale co bot!',
                color=self.bot.COLOR,
            )
        elif ctx.voice_client.is_playing():
            embed = discord.Embed(
                title=':red_circle: Nie wznowiono odtwarzania utworu, bo już jest odtwarzany', color=self.bot.COLOR
            )
        elif not ctx.voice_client.is_paused():
            embed = discord.Embed(
                title=':red_circle: Nie wznowiono odtwarzania utworu, bo żaden nie jest spauzowany',
                color=self.bot.COLOR,
            )
        else:
            ctx.voice_client.resume()
            embed = discord.Embed(title=f':arrow_forward: Wznowiono odtwarzanie utworu', color=self.bot.COLOR)
        await self.bot.send(ctx, embed=embed)

    @disco.command(aliases=['pomiń', 'pomin', 'skip'])
    @cooldown()
    @commands.guild_only()
    async def disco_skip(self, ctx):
        """Skips the currently played song."""
        if ctx.voice_client is None:
            embed = discord.Embed(
                title=':red_circle: Nie pominięto utworu, bo bot nie jest połączony z żadnym kanałem głosowym',
                color=self.bot.COLOR,
            )
        elif not ctx.voice_client.is_playing() and not ctx.voice_client.is_paused():
            embed = discord.Embed(
                title=':red_circle: Nie pominięto utworu, bo żaden nie jest odtwarzany', color=self.bot.COLOR
            )
        else:
            ctx.voice_client.stop()
            embed = discord.Embed(title=f':fast_forward: Pominięto utwór', color=self.bot.COLOR)
        await self.bot.send(ctx, embed=embed)

    @disco.command(aliases=['rozłącz', 'rozlacz', 'stop'])
    @cooldown()
    @commands.guild_only()
    async def disco_disconnect(self, ctx):
        """Disconnects from the server."""
        if ctx.voice_client is None:
            embed = discord.Embed(
                title=':warning: Nie rozłączono z kanałem głosowym, bo bot nie jest połączony z żadnym!',
                color=self.bot.COLOR,
            )
        elif (
            ctx.author.voice is None or (ctx.me.voice.channel and ctx.author.voice.channel != ctx.me.voice.channel)
        ) and len(ctx.me.voice.channel.members) > 1:
            embed = discord.Embed(
                title=':warning: Odtwarzanie można kontrolować tylko będąc na tym samym kanale co bot!',
                color=self.bot.COLOR,
            )
        else:
            voice_channel = await self.server_disconnect(ctx.guild)
            embed = discord.Embed(title=f':stop_button: Rozłączono z kanałem {voice_channel}', color=self.bot.COLOR)
        await self.bot.send(ctx, embed=embed)

    @disco.command(aliases=['głośność', 'glosnosc', 'poziom', 'volume', 'vol'])
    @cooldown()
    @commands.guild_only()
    async def disco_volume(self, ctx, volume_percentage: Union[int, locale.atoi] = None):
        """Sets the volume."""
        if volume_percentage is None:
            embed = discord.Embed(
                title=':level_slider: Głośność ustawiona jest na '
                f'{int(self.servers[ctx.guild.id]["volume"] * 100)}%',
                color=self.bot.COLOR,
            )
        else:
            if ctx.voice_client is not None and (
                ctx.author.voice is None or (ctx.me.voice.channel and ctx.author.voice.channel != ctx.me.voice.channel)
            ):
                embed = discord.Embed(
                    title=':warning: Odtwarzanie można kontrolować tylko będąc na tym samym kanale co bot!',
                    color=self.bot.COLOR,
                )
            else:
                self.server_change_volume(ctx.guild, volume_percentage)
                embed = discord.Embed(
                    title=':level_slider: Ustawiono głośność na ' f'{int(self.servers[ctx.guild.id]["volume"] * 100)}%',
                    color=self.bot.COLOR,
                )
        await self.bot.send(ctx, embed=embed)

    @disco_volume.error
    async def disco_volume_error(self, ctx, error):
        if isinstance(error, commands.BadUnionArgument):
            embed = discord.Embed(title=f':warning: Podana wartość nie jest liczbą całkowitą!', color=self.bot.COLOR)
            await self.bot.send(ctx, embed=embed)
예제 #2
0
class Colors(commands.Cog):
    GROUP = Help.Command(
        ('kolory', 'kolor', 'kolorki', 'kolorek'), (),
        'Komendy związane z kolorami nicków samodzielnie wybieranymi przez użytkowników. '
        'Odbywa się to z użyciem ról o nazwach zaczynających się emoji "🎨".')
    COMMANDS = (
        Help.Command(('role', 'lista'), (),
                     'Zwraca listę dostępnych kolorów–ról.'),
        Help.Command('ustaw', 'kolor–rola',
                     'Ustawia ci wybrany <kolor–rolę>.'),
        Help.Command(
            'pokaż', '?użytkownik/kolor–rola/reprezentacja szesnastkowa',
            'Pokazuje kolor–rolę <użytkownika>, <kolor–rolę> lub kolor wyrażony podaną <reprezentacją szesnastkową>. '
            'Jeśli nie podano <?użytkownika/koloru–roli/reprezentacji szesnastkowej>, pokazuje twój kolor–rolę.'
        ), Help.Command(('wyczyść', 'wyczysc'), (), 'Wyczyszcza twój kolor.'))
    HELP = Help(COMMANDS, '🎨', group=GROUP)
    GRAY = 0xcdd7de

    def __init__(self, bot: commands.Bot):
        self.bot = bot

    @commands.group(invoke_without_command=True,
                    case_insensitive=True,
                    aliases=['kolory', 'kolor', 'kolorki', 'kolorek'])
    @cooldown()
    @commands.guild_only()
    async def colors(self, ctx):
        await self.bot.send(ctx, embeds=self.HELP.embeds)

    @colors.command(aliases=['role', 'lista'])
    @cooldown()
    @commands.guild_only()
    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)

    @colors.command(aliases=['ustaw'])
    @cooldown()
    @commands.guild_only()
    async def set(self,
                  ctx,
                  *,
                  role_candidate: Union[discord.Role, str] = '?'):
        role = None
        color = None
        description = None
        is_random = False
        if isinstance(role_candidate, str):
            is_random = all((character == '?' for character in role_candidate))
            relevant_roles = list(
                filter(lambda role: role.name.startswith('🎨'),
                       ctx.guild.roles))
            if is_random and relevant_roles:
                role = random.choice(relevant_roles)
            else:
                role_name = role_candidate.lstrip('🎨').lstrip().lower()
                for this_role in ctx.guild.roles:
                    if this_role.name.startswith(
                            '🎨') and this_role.name.lstrip(
                                '🎨').lstrip().lower() == role_name:
                        role = this_role
                        break
        elif isinstance(role_candidate,
                        discord.Role) and role_candidate.name.startswith('🎨'):
            role = role_candidate
        if role is None:
            emoji, notice = '❔', 'Nie znaleziono pasującego koloru–roli'
            color = self.GRAY
        else:
            role_name = role.name.lstrip('🎨').lstrip()
            already_present = False
            roles_for_removal = []
            for this_role in ctx.author.roles:
                if this_role.name.startswith('🎨'):
                    if this_role == role:
                        already_present = True
                    else:
                        roles_for_removal.append(this_role)
            try:
                roles_counter = Counter((role for member in ctx.guild.members
                                         for role in member.roles))
                if roles_for_removal:
                    await ctx.author.remove_roles(*roles_for_removal)
                if not already_present:
                    await ctx.author.add_roles(role)
                    roles_counter[role] += 1
                    if is_random:
                        emoji, notice = '🎲', f'Wylosowano ci kolor–rolę {role_name}'
                    else:
                        emoji, notice = '✅', f'Ustawiono ci kolor–rolę {role_name}'
                else:
                    emoji, notice = 'ℹ️', f'Już masz kolor–rolę {role_name}'
                description = f'{role.mention} – `{str(role.color).upper()}` – 👥 {roles_counter[role]}'
            except discord.Forbidden:
                emoji, notice = '⚠️', 'Bot nie ma wymaganych do tego uprawnień (zarządzanie rolami)'
            else:
                color = role.color
        embed = self.bot.generate_embed(emoji,
                                        notice,
                                        description,
                                        color=color)
        await self.bot.send(ctx, embed=embed)

    @colors.command(aliases=['pokaż', 'pokaz'])
    @cooldown()
    @commands.guild_only()
    async def show(self,
                   ctx,
                   *,
                   subject_candidate: Union[discord.Member, discord.Role,
                                            str] = None):
        subject_candidate = subject_candidate or ctx.author
        role = None
        color = None
        about_member = None
        if isinstance(subject_candidate, discord.Member):
            for this_role in subject_candidate.roles:
                if this_role.name.startswith('🎨'):
                    role = this_role
                    break
            about_member = subject_candidate
        elif isinstance(
                subject_candidate,
                discord.Role) and subject_candidate.name.startswith('🎨'):
            role = subject_candidate
        elif isinstance(subject_candidate, str):
            hex_candidate = subject_candidate.lstrip('#')
            if len(hex_candidate) == 3:
                hex_candidate = ''.join(
                    itertools.chain.from_iterable(
                        zip(hex_candidate, hex_candidate)))
            if len(hex_candidate) == 6:
                try:
                    color = int(hex_candidate, 16)
                except ValueError:
                    pass
            if color is not None:
                for this_role in ctx.guild.roles:
                    if this_role.color.value == color:
                        role = this_role
                        break
            else:
                role_name = subject_candidate.lstrip('🎨').lstrip().lower()
                for this_role in ctx.guild.roles:
                    if this_role.name.startswith(
                            '🎨') and this_role.name.lstrip(
                                '🎨').lstrip().lower() == role_name:
                        role = this_role
                        break
        if role is not None:
            roles_counter = Counter((role for member in ctx.guild.members
                                     for role in member.roles))
            description = f'{role.mention} – `{str(role.color).upper()}` – 👥 {roles_counter[role]}'
            color = role.color
            emoji = '🎨'
            if about_member is not None:
                address = 'Masz' if about_member == ctx.author else f'{about_member} ma'
                notice = f'{address} kolor–rolę {role.name.lstrip("🎨").lstrip()}'
            else:
                notice = f'Kolor–rola {role.name.lstrip("🎨").lstrip()}'
        elif color is not None:
            emoji, notice, description = '🎨', f'Kolor #{hex_candidate.upper()}', '← Widoczny na pasku z boku.'
        else:
            description = None
            if about_member is not None:
                address = 'Nie masz' if about_member == ctx.author else f'{about_member} nie ma'
                emoji, notice = '❔', f'{address} koloru–roli'
                color = self.GRAY
            else:
                emoji, notice = '⚠️', 'Nie rozpoznano użytkownika, koloru–roli ani reprezentacji szesnastkowej'
                color = None
        embed = self.bot.generate_embed(emoji,
                                        notice,
                                        description,
                                        color=color)
        await self.bot.send(ctx, embed=embed)

    @colors.command(aliases=['wyczyść', 'wyczysc'])
    @cooldown()
    @commands.guild_only()
    async def clear(self, ctx):
        roles_for_removal = [
            role for role in ctx.author.roles if role.name.startswith('🎨')
        ]
        color = self.GRAY
        if roles_for_removal:
            try:
                await ctx.author.remove_roles(*roles_for_removal)
            except discord.Forbidden:
                emoji, notice = '⚠️', 'Bot nie ma wymaganych do tego uprawnień (zarządzanie rolami)'
                color = None
            else:
                emoji, notice = '✅', 'Usunięto twój kolor–rolę'
        else:
            emoji, notice = 'ℹ️', 'Nie masz koloru–roli do usunięcia'
        embed = self.bot.generate_embed(emoji, notice, color=color)
        await self.bot.send(ctx, embed=embed)
예제 #3
0
class TMDb(commands.Cog):
    GROUP = Help.Command(
        'tmdb', (),
        'Komendy związane z informacjami o produkcjach i ludziach ze światów kina i telewizji. '
        'Użyj <?zapytania> zamiast <?podkomendy>, by otrzymać najlepiej pasujący film/serial/osobę.'
    )
    COMMANDS = (
        Help.Command(('film', 'kino'), 'zapytanie', 'Zwraca najlepiej pasujący do <zapytania> film.'),
        Help.Command(
            ('serial', 'seria', 'telewizja', 'tv'), 'zapytanie', 'Zwraca najlepiej pasujący do <zapytania> serial.'
        ),
        Help.Command('osoba', 'zapytanie', 'Zwraca najlepiej pasującą do <zapytania> osobę.')
    )
    HELP = Help(
        COMMANDS, '🎬', group=GROUP, footer_text='TMDb',
        footer_icon_url='https://www.themoviedb.org/assets/2/v4/logos/'
        '208x226-stacked-green-9484383bd9853615c113f020def5cbe27f6d08a84ff834f41371f223ebad4a3c.png'
    )
    PROFESSIONS = {
        'Acting': '🎭', 'Art': '🎨', 'Camera': '🎥', 'Costume': '👗', 'Creator': '🧠', 'Crew': '🔧', 'Directing': '🎬',
        'Editing': '✂️', 'Lighting': '💡', 'Production': '📈', 'Sound': '🎙', 'Visual Effects': '🎇', 'Writing': '🖋'
    }

    def __init__(self, bot: commands.Bot):
        self.bot = bot

    async def fetch_result_and_generate_embed(self, query: str, media_type: Optional[str] = None) -> discord.Embed:
        params = {'api_key': configuration['tmdb_key'], 'query': query, 'language': 'pl-PL', 'region': 'PL'}
        search_url = f'https://api.themoviedb.org/3/search/{media_type or "multi"}'
        async with self.bot.session.get(search_url, params=params) as request:
            if request.status != 200:
                embed = self.bot.generate_embed('⚠️', f'Nie udało się połączyć z serwisem')
            else:
                response = await request.json()
                if not response['total_results']:
                    embed = self.bot.generate_embed('🙁', f'Brak wyników dla zapytania "{query}"')
                else:
                    search_result = response['results'][0]
                    media_type = media_type or search_result['media_type']
                    async with self.bot.session.get(
                        f'https://api.themoviedb.org/3/{media_type}/{search_result["id"]}', params=params
                    ) as request:
                        if request.status != 200:
                            embed = self.bot.generate_embed('⚠️', f'Nie udało się połączyć z serwisem')
                        else:
                            full_result = await request.json()
                            full_result.update(search_result)
                            if media_type == 'person':
                                embed = self.generate_person_embed(full_result)
                            elif media_type == 'movie':
                                embed = self.generate_movie_embed(full_result)
                            elif media_type == 'tv':
                                embed = self.generate_tv_embed(full_result)
        embed.set_footer(
            text='TMDb',
            icon_url='https://www.themoviedb.org/assets/2/v4/logos/'
            '208x226-stacked-green-9484383bd9853615c113f020def5cbe27f6d08a84ff834f41371f223ebad4a3c.png'
        )
        return embed

    def generate_person_embed(self, result: dict) -> discord.Embed:
        is_female = result['gender'] == 1
        today = dt.date.today()
        birth_date = dt.date.fromisoformat(result['birthday']) if result['birthday'] else None
        death_date = dt.date.fromisoformat(result['deathday']) if result['deathday'] else None
        if birth_date is not None and (birth_date.month, birth_date.day) == (today.month, today.day):
            emoji = '🎂'
        else:
            emoji = self.PROFESSIONS.get(result['known_for_department']) or ('👩' if is_female else '👨')
        embed = self.bot.generate_embed(emoji, result['name'], url=f'https://www.themoviedb.org/person/{result["id"]}')
        if birth_date is not None:
            embed.add_field(name='Data urodzenia', value=birth_date.strftime('%-d %B %Y'))
        if death_date is not None:
            embed.add_field(name='Data śmierci', value=death_date.strftime('%-d %B %Y'))
        if birth_date is not None:
            embed.add_field(name='Wiek', value=calculate_age(birth_date, death_date))
        known_for_parts = (
            f'[📺 {production["name"]} ({production["first_air_date"][:4]})]'
            f'(https://www.themoviedb.org/tv/{production["id"]})'
            if production['media_type'] == 'tv' else
            f'[🎞 {production["title"]} ({production["release_date"][:4]})]'
            f'(https://www.themoviedb.org/movie/{production["id"]})'
            for production in result['known_for']
        )
        if result['biography']:
            embed.add_field(name='Biografia', value=result['biography'], inline=False)
        embed.add_field(
            name='Znana z' if is_female else 'Znany z', value='\n'.join(known_for_parts), inline=False
        )
        if result['profile_path']:
            embed.set_thumbnail(url=f'https://image.tmdb.org/t/p/w342{result["profile_path"]}')
        if result['known_for'] and result['known_for'][0].get('backdrop_path'):
            embed.set_image(url=f'https://image.tmdb.org/t/p/w780{result["known_for"][0]["backdrop_path"]}')
        return embed

    def generate_movie_embed(self, result: dict) -> discord.Embed:
        release_date = dt.date.fromisoformat(result['release_date']) if result['release_date'] else None
        embed = self.bot.generate_embed(
            '🎞', f'{result["title"]} ({result["release_date"][:4]})', result.get('tagline'),
            url=f'https://www.themoviedb.org/movie/{result["id"]}'
        )
        if result.get('original_title') != result['title']:
            embed.add_field(name='Tytuł oryginalny', value=result['original_title'], 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 release_date is not None:
            embed.add_field(name='Data premiery', value=release_date.strftime('%-d %B %Y'))
        if result['runtime']:
            embed.add_field(name='Długość', value=human_amount_of_time(result['runtime'] * 60))
        if result['budget']:
            embed.add_field(name='Budżet', value=f'${result["budget"]:n}')
        if result['revenue']:
            embed.add_field(name='Przychody', value=f'${result["revenue"]:n}')
        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)))
        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

    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

    @commands.group(invoke_without_command=True)
    @cooldown()
    async def tmdb(self, ctx, *, query):
        """Responds with the most popular movie/series/person matching the query."""
        async with ctx.typing():
            embed = await self.fetch_result_and_generate_embed(query)
            await self.bot.send(ctx, embed=embed)

    @tmdb.error
    async def tmdb_error(self, ctx, error):
        if isinstance(error, commands.MissingRequiredArgument):
            await self.bot.send(ctx, embeds=self.HELP.embeds)

    @tmdb.command(aliases=['film', 'kino'])
    @cooldown()
    async def movie(self, ctx, *, query):
        """Responds with the most popular movie matching the query."""
        async with ctx.typing():
            embed = await self.fetch_result_and_generate_embed(query, 'movie')
            await self.bot.send(ctx, embed=embed)

    @commands.command(aliases=['movie', 'film', 'kino'])
    @cooldown()
    async def movie_unbound(self, ctx, *, query):
        """Responds with the most popular movie matching the query."""
        await ctx.invoke(self.movie, query=query)

    @tmdb.command(aliases=['serial', 'seria', 'telewizja'])
    @cooldown()
    async def tv(self, ctx, *, query):
        """Responds with the most popular TV series matching the query."""
        async with ctx.typing():
            embed = await self.fetch_result_and_generate_embed(query, 'tv')
            await self.bot.send(ctx, embed=embed)

    @commands.command(aliases=['tv', 'serial', 'seria', 'telewizja'])
    @cooldown()
    async def tv_unbound(self, ctx, *, query):
        """Responds with the most popular TV series matching the query."""
        await ctx.invoke(self.tv, query=query)

    @tmdb.command(aliases=['osoba'])
    @cooldown()
    async def person(self, ctx, *, query):
        """Responds with the most popular person matching the query."""
        async with ctx.typing():
            embed = await self.fetch_result_and_generate_embed(query, 'person')
            await self.bot.send(ctx, embed=embed)

    @commands.command(aliases=['person', 'osoba'])
    @cooldown()
    async def person_unbound(self, ctx, *, query):
        """Responds with the most popular person matching the query."""
        await ctx.invoke(self.person, query=query)
예제 #4
0
class Help(commands.Cog):
    COMMANDS = (
        _Help.Command(('pomocy', 'pomoc', 'help'), (),
                      'Wysyła ci wiadomość pomocy z objaśnieniem komend.',
                      '❓'),
        _Help.Command(('8-ball', '8ball', 'eightball', '8', 'czy'), 'pytanie',
                      'Zadaje <pytanie> magicznej kuli.', '🎱'),
        _Help.Command(
            ('wybierz', ),
            ('opcje', ),
            'Wybiera jedną z oddzielonych przecinkami, średnikami, "lub", "albo" bądź "czy" <opcji>.',
            '👉',
            ['wybierz 420, 69, 666', 'wybierz pies czy kot'],
        ),
        _Help.Command(('rzuć', 'rzuc'),
                      ('?liczba kości', '?liczba ścianek kości'),
                      'Rzuca kością/koścmi.', '🎲'),
        _Help.Command(
            ('google', 'gugiel', 'g'),
            'zapytanie',
            'Wysyła <zapytanie> do [Google](https://www.google.com) i zwraca najlepiej pasującą stronę.',
            '🇬',
        ),
        _Help.Command(
            ('googleimage', 'gi', 'i'),
            'zapytanie',
            'Wysyła <zapytanie> do [Google](https://www.google.pl/imghp) i zwraca najlepiej pasujący obrazek.',
            '🖼',
        ),
        _Help.Command(
            ('youtube', 'yt', 'tuba'),
            'zapytanie',
            'Zwraca z [YouTube](https://www.youtube.com) najlepiej pasujące do <zapytania> wideo.',
            '▶️',
        ),
        _Help.Command(
            ('wikipedia', 'wiki', 'w'),
            ('dwuliterowy kod języka', 'hasło'),
            'Sprawdza znaczenie <hasła> w danej wersji językowej [Wikipedii](https://www.wikipedia.org/).',
            '📖',
        ),
        _Help.Command(
            'tmdb',
            'zapytanie/podkomenda',
            'Zwraca z [TMDb](https://www.themoviedb.org/) najlepiej pasujący do <?zapytania> film/serial/osobę. '
            'Użyj bez <?zapytania/podkomendy>, by dowiedzieć się więcej.',
            '🎬',
        ),
        _Help.Command(
            'spotify',
            '?użytkownik Discorda',
            'Zwraca informacje na temat utworu obecnie słuchanego przez <?użytkownika Discorda> na Spotify. '
            'Jeśli nie podano <?użytkownika Discorda>, przyjmuje ciebie.',
            '🎶',
        ),
        _Help.Command(
            ('lastfm', 'last', 'fm', 'lfm'),
            'użytkownik Last.fm',
            'Zwraca z Last.fm informacje na temat utworu obecnie słuchanego przez <użytkownika Last.fm>.',
            '🎧',
        ),
        _Help.Command(
            ('goodreads', 'gr', 'książka', 'ksiazka'),
            'tytuł/autor',
            'Zwraca z [goodreads](https://www.goodreads.com) informacje na temat książki najlepiej pasującej do '
            '<tytułu/autora>.',
            '📚',
        ),
        _Help.Command(
            ('urbandictionary', 'urban'),
            'wyrażenie',
            'Sprawdza znaczenie <wyrażenia> w [Urban Dictionary](https://www.urbandictionary.com).',
            '📔',
        ),
        _Help.Command(
            ('wolframalpha', 'wolfram', 'wa', 'kalkulator', 'oblicz', 'policz',
             'przelicz', 'konwertuj'),
            ('zapytanie', ),
            '[Wolfram Alpha](https://www.wolframalpha.com/) – oblicza, przelicza, podaje najróżniejsze informacje. '
            'Usługa po angielsku.',
            '🧠',
        ),
        _Help.Command(('isitup', 'isup', 'czydziała', 'czydziala'), 'link',
                      'Sprawdza status danej strony.', '🚦'),
        _Help.Command(
            ('rokszkolny', 'wakacje', 'ilejeszcze'),
            '?podkomenda',
            'Zwraca ile jeszcze zostało do końca roku szkolnego lub wakacji. '
            'Użyj z <podkomendą> "matura", by dowiedzieć się ile zostało do matury.',
            '🎓',
        ),
        _Help.Command(('subreddit', 'sub', 'r'), 'subreddit',
                      'Zwraca informacje o <subreddicie>.', '🛸'),
        _Help.Command(('user', 'u'), 'użytkownik Reddita',
                      'Zwraca informacje o <użytkowniku Reddita>.', '👽'),
        _Help.Command(
            ('disco', 'd'),
            '?podkomenda',
            'Komendy związane z odtwarzaniem muzyki na kanałach głosowych. Użyj bez <?podkomendy>, by dowiedzieć się więcej.',
            '📻',
        ),
        _Help.Command(
            'role', (),
            'Wyświetla wszystkie role na serwerze wraz z liczbą członków je mających.',
            '🔰'),
        _Help.Command(
            ('stat', 'staty', 'aktywnosć', 'aktywnosc'),
            '?użytkownik/kanał/kategoria/podkomenda',
            'Komendy związane z raportami serwerowych statystyk. '
            'Użyj bez <?użytkownika/kanału/kategorii/podkomendy>, by dowiedzieć się więcej.',
            '📈',
        ),
        _Help.Command(
            'urodziny',
            '?podkomenda/użytkownik',
            'Komendy związane z datami urodzin. Użyj bez <?podkomendy/użytkownika>, by dowiedzieć się więcej.',
            '🎂',
        ),
        _Help.Command(
            ('handlowe', 'niedzielehandlowe'),
            '?podkomenda',
            'Komendy związane z niedzielami handlowymi. Użyj bez <?podkomendy>, by dowiedzieć się więcej.',
            '🛒',
        ),
        _Help.Command(
            ('przypomnij', 'przypomnienie', 'pomidor'),
            ('liczba minut/data i godzina', 'treść'),
            'Przypomina o <treści> po upływie podanego czasu.',
            '🍅',
            ['przypomnij 21.08.2022T12:00 Wyłączyć piekarnik!'],
        ),
        _Help.Command(
            ('spal', 'burn'),
            ('liczba minut/data i godzina',
             '?treść – może to też być załącznik'),
            'Usuwa wiadomość po upływie podanego czasu.',
            '🔥',
            ['spal 2h Nudesy'],
        ),
        _Help.Command(
            ('kolory', 'kolor', 'kolorki', 'kolorek'),
            '?podkomenda',
            'Komendy związane z kolorami nicków samodzielnie wybieranymi przez użytkowników. '
            'Użyj bez <?podkomendy>, by dowiedzieć się więcej.',
            '🎨',
        ),
        _Help.Command(
            'przypinki',
            '?podkomenda',
            'Komendy związane z archiwizacją przypiętych wiadomości. '
            'Użyj bez <?podkomendy>, by dowiedzieć się więcej.',
            '📌',
        ),
        _Help.Command(
            ('głosowanie', 'glosowanie'),
            ('?liczba minut/data i godzina', 'sprawa'),
            'Przeprowadza głosowanie za/przeciw dotyczące <sprawy>. '
            'Ogłasza wynik po upływie podanego czasu, jeśli go podano.',
            '🗳',
        ),
        _Help.Command(('pomógł', 'pomogl'), '?użytkownik Discorda',
                      'Oznacza pomocną wiadomość za pomocą reakcji.', '😺'),
        _Help.Command(('niepomógł', 'niepomogl'), '?użytkownik Discorda',
                      'Oznacza niepomocną wiadomość za pomocą reakcji.', '😾'),
        _Help.Command(
            ('hm', 'hmm', 'hmmm', 'hmmmm', 'hmmmmm', 'myśl', 'mysl', 'think',
             'thinking', '🤔'),
            '?użytkownik Discorda',
            '🤔',
            '🤔',
        ),
        _Help.Command(('^', 'to', 'this', 'up', 'upvote'),
                      '?użytkownik Discorda', '⬆', '⬆'),
        _Help.Command('f', '?użytkownik Discorda', 'F', '🇫'),
        _Help.Command(
            ('zareaguj', 'reaguj', 'x'),
            ('?użytkownik Discorda', 'reakcje'),
            'Dodaje <reakcje> do ostatniej wiadomości wysłanej na kanale '
            '(jeśli podano <?użytkownika Discorda>, to ostatnią jego autorstwa na kanale).',
            '💬',
        ),
        _Help.Command('oof', (), 'Oof!', '😤'),
        _Help.Command(
            'oof ile',
            '?użytkownik Discorda',
            'Zlicza oofnięcia dla <?użytkownika Discorda> lub, jeśli nie podano <?użytkownika Discorda>, dla ciebie.',
            '😱',
        ),
        _Help.Command(
            'oof serwer', (),
            'Zlicza oofnięcia na serwerze i generuje ranking ooferów.', '🤠'),
        _Help.Command(
            ('obróć', 'obroc', 'niewytrzymie'),
            ('?użytkownik', '?stopni/razy'),
            'Obraca ostatni załączony na kanale lub, jeśli podano <?użytkownika>, na kanale przez <?użytkownika> obrazek <?stopni/razy> (domyślnie 90 stopni/1 raz) zgodnie z ruchem wskazówek zegara.',
            '🔁',
        ),
        _Help.Command(
            ('deepfry', 'usmaż', 'głębokosmaż', 'usmaz', 'glebokosmaz'),
            ('?użytkownik', '?poziom usmażenia'),
            'Smaży ostatni załączony na kanale lub, jeśli podano <?użytkownika>, na kanale przez <?użytkownika> obrazek <?poziom usmażenia> razy (domyślnie 2 razy).',
            '🍟',
        ),
        _Help.Command(
            ('robot9000', 'r9k', 'było', 'bylo', 'byo'),
            '?użytkownik',
            'Sprawdza czy ostatnio załączony na kanale lub, jeśli podano <?użytkownika>, na kanale przez <?użytkownika> obrazek pojawił się wcześniej na serwerze.',
            '🤖',
        ),
        _Help.Command('tableflip', (), '(╯°□°)╯︵ ┻━┻', '🤬'),
        _Help.Command('unflip', (), '┬─┬ ノ( ゜-゜ノ)', '😞'),
        _Help.Command('shrug', (), r'¯\_(ツ)_/¯', '🤷'),
        _Help.Command(('lenny', 'lennyface'), (), '( ͡° ͜ʖ ͡°)', '😏'),
        _Help.Command(('lenno', 'lennoface'), (), '( ͡ʘ ͜ʖ ͡ʘ)', '😼'),
        _Help.Command(('dej', 'gib'), '?rzecz', '༼ つ ◕_◕ ༽つ <?rzecz>', '🤲'),
        _Help.Command(
            ('nie', 'nope', 'no'),
            (),
            'Usuwa ostatnią wiadomość wysłaną przez bota na kanale jako rezultat użytej przez ciebie komendy.',
            '🗑',
        ),
        _Help.Command(
            ('warn', 'ostrzeż', 'ostrzez'),
            ('użytkownik Discorda', 'powód'),
            'Ostrzega <użytkownika Discorda>. '
            'Działa tylko dla członków serwera mających uprawnienia do wyrzucania innych.',
            '❗️',
        ),
        _Help.Command(
            ('kick', 'wyrzuć', 'wyrzuc'),
            ('użytkownik Discorda', 'powód'),
            'Wyrzuca <użytkownika Discorda>. '
            'Działa tylko dla członków serwera mających uprawnienia do wyrzucania innych.',
            '👋',
        ),
        _Help.Command(
            ('ban', 'zbanuj'),
            ('użytkownik Discorda', 'powód'),
            'Banuje <użytkownika Discorda>. Działa tylko dla członków serwera mających uprawnienia do banowania innych.',
            '🔨',
        ),
        _Help.Command(
            ('przebacz', 'pardon'),
            ('użytkownik Discorda'),
            'Usuwa wszystkie ostrzeżenia <użytkownika Discorda> na serwerze. '
            'Działa tylko dla członków serwera mających uprawnienia administratora.',
            '🕊',
        ),
        _Help.Command(
            'kartoteka',
            ('?użytkownik Discorda', '?typy zdarzeń'),
            'Sprawdza kartotekę <?użytkownika Discorda> pod kątem <?typów zdarzeń>. '
            'Jeśli nie podano <?użytkownika Discorda>, przyjmuje ciebie. '
            'Jeśli nie podano typu <?typów zdarzeń>, zwraca wszystkie zdarzenia.',
            '📂',
        ),
        _Help.Command(
            ('wyczyść', 'wyczysc'),
            '?liczba',
            'Usuwa <?liczbę> ostatnich wiadomości z kanału lub, jeśli nie podano <?liczby>, jedną ostatnią wiadomość '
            'z kanału na którym użyto komendy. Działa tylko dla członków serwera mających uprawnienia '
            'do zarządzania wiadomościami na kanale.',
            '🧹',
        ),
        _Help.Command(
            ('prefiks', 'prefix'),
            '?podkomenda',
            'Komendy związane z własnymi serwerowymi prefiksami komend. '
            'Użyj bez <?podkomendy>, by dowiedzieć się więcej.',
            '🔧',
        ),
        _Help.Command(('komendadnia', 'cotd'), (),
                      'Pokazuje dzisiejszą komendę dnia.', '👀'),
        _Help.Command(('ping', 'pińg'), (), 'Pong!', '🏓'),
        _Help.Command(('wersja', 'v'), (), 'Pokazuje działającą wersja bota.',
                      '🍆'),
        _Help.Command(
            ('informacje', 'info'), (),
            'Pokazuje działającą wersja bota plus dodatkowe informacje.',
            'ℹ️'),
    )
    DESCRIPTION = (
        'Somsiad jestem. Pomagam ludziom w różnych kwestiach. '
        'By skorzystać z mojej pomocy wystarczy wysłać komendę w miejscu, w którym będę mógł ją zobaczyć. '
        'Lista komend wraz z ich opisami znajduje się poniżej. '
        'Używając ich na serwerach pamiętaj o prefiksie (możesz zawsze sprawdzić go za pomocą '
        f'`{configuration["command_prefix"]}prefiks sprawdź`).\n'
        'W (nawiasach okrągłych) podane są aliasy komend.\n'
        'W <nawiasach ostrokątnych> podane są argumenty komend. Jeśli przed nazwą argumentu jest ?pytajnik, '
        'oznacza to, że jest to argument opcjonalny.')

    def __init__(self, bot: Somsiad):
        self.bot = bot
        description = self.DESCRIPTION + f'\nBy dowiedzieć się o mnie więcej, wejdź na {self.bot.WEBSITE_URL}.'
        self.HELP = _Help(self.COMMANDS, '👋', 'Dobry!', description)
        self.auto_command_of_the_day.start()

    def cog_unload(self):
        self.auto_command_of_the_day.cancel()

    @commands.command(aliases=['cotd', 'komendadnia'])
    @cooldown()
    async def command_of_the_day(self, ctx):
        await self.bot.send(ctx, embed=self.compose_command_of_the_day_embed())

    @tasks.loop(hours=24)
    async def auto_command_of_the_day(self):
        if self.bot.public_channel:
            await self.bot.public_channel.send(
                embed=self.compose_command_of_the_day_embed())

    @auto_command_of_the_day.before_loop
    async def before_command_of_the_day(self):
        now = dt.datetime.now().astimezone()
        next_iteration_moment = dt.datetime(now.year, now.month, now.day,
                                            *COTD_TIME).astimezone()
        if next_iteration_moment != now:
            if next_iteration_moment < now:
                next_iteration_moment += dt.timedelta(1)
            await discord.utils.sleep_until(next_iteration_moment)

    def compose_command_of_the_day_embed(self) -> discord.Embed:
        today = dt.date.today()
        today_number = (today.year * 10_000 + today.month * 100 + today.day
                        )  # Eg. datetime 2024-07-30 is integer 20240730
        command_help_hash = int.from_bytes(hashlib.sha1(
            today_number.to_bytes(8, "big", signed=False)).digest(),
                                           "big",
                                           signed=False)
        command_help = self.COMMANDS[command_help_hash % len(self.COMMANDS)]
        today_name_days = NAME_DAYS[today.month][today.day]
        trade_sunday_info = ''
        if today.weekday() == 6:
            is_todays_sunday_trade = determine_nearest_trade_sunday_after_date(
                today) == today
            trade_sunday_info = " (handlowa)" if is_todays_sunday_trade else " (bez handlu)"
        embed = self.bot.generate_embed(
            command_help.emoji,
            f"Komenda dnia: {command_help.name}",
            f"{today.strftime(f'%A{trade_sunday_info}, %-d %B %Y').capitalize()}. Imieniny {join_using_and(today_name_days)}.",
        )
        embed.add_field(name=str(command_help),
                        value=command_help.description,
                        inline=False)
        return embed

    @commands.command(aliases=['help', 'pomocy', 'pomoc'])
    @cooldown()
    async def help_message(self, ctx):
        await self.bot.send(ctx, direct=True, embed=self.HELP.embeds)
예제 #5
0
class Birthday(commands.Cog):
    DATE_WITH_YEAR_FORMATS = (
        '%d %m %Y',
        '%Y %m %d',
        '%d %B %Y',
        '%d %b %Y',
        '%d %m %y',
        '%y %m %d',
        '%d %B %y',
        '%d %b %y',
    )
    DATE_WITHOUT_YEAR_FORMATS = ('%d %m', '%d %B', '%d %b')
    MONTH_FORMATS = ('%m', '%B', '%b')
    NOTIFICATIONS_TIME = (8, 0)

    GROUP = Help.Command(
        'urodziny',
        (),
        'Komendy związane z datami urodzin. '
        'Użyj <?użytkownika> zamiast <?podkomendy>, by sprawdzić datę urodzin.',
    )
    COMMANDS = (
        Help.Command(('zapamiętaj', 'zapamietaj', 'ustaw'), 'data',
                     'Zapamiętuje twoją datę urodzin.'),
        Help.Command('zapomnij', (), 'Zapomina twoją datę urodzin.'),
        Help.Command(
            'upublicznij',
            (),
            'Upublicznia twoją datę urodzin na serwerze. '
            'Dzięki temu mogą też działać serwerowe powiadomienia o urodzinach.',
        ),
        Help.Command('utajnij', (), 'Utajnia twoją datę urodzin na serwerze.'),
        Help.Command(
            'gdzie', (),
            'Informuje na jakich serwerach twoja data urodzin jest w tym momencie publiczna.'
        ),
        Help.Command(
            ('lista', 'serwer', 'wszyscy', 'wszystkie', 'kalendarz'),
            (),
            'Pokazuje listę użytkowników wraz z datami urodzin.',
        ),
        Help.Command(
            ('kiedy', 'sprawdz', 'sprawdź', 'pokaz', 'pokaż'),
            '?użytkownik',
            'Zwraca datę urodzin <?użytkownika>. Jeśli nie podano <?użytkownika>, przyjmuje ciebie.',
        ),
        Help.Command(
            'wiek', '?użytkownik',
            'Zwraca wiek <?użytkownika>. Jeśli nie podano <?użytkownika>, przyjmuje ciebie.'
        ),
        Help.Command(
            'powiadomienia',
            '?podkomenda',
            'Komendy związane z powiadamianiem na serwerze o dzisiejszych urodzinach członków. '
            'Użyj bez <?podkomendy>, by dowiedzieć się więcej. Wymaga uprawnień administratora.',
        ),
    )
    HELP = Help(COMMANDS, '🎂', group=GROUP)

    NOTIFICATIONS_GROUP = Help.Command(
        'urodziny powiadomienia',
        (),
        'Komendy związane z powiadamianiem na serwerze o dzisiejszych urodzinach członków. '
        'Wymaga uprawnień administratora.',
    )
    NOTIFICATIONS_COMMANDS = (
        Help.Command(
            'status', (),
            'Informuje czy powiadomienia o urodzinach są włączone i na jaki kanał są wysyłane.'
        ),
        Help.Command(
            ('włącz', 'wlacz'),
            '?kanał',
            'Ustawia <?kanał> jako kanał powiadomień o dzisiejszych urodzinach. '
            'Jeśli nie podano <?kanału>, przyjmuje te na którym użyto komendy.',
        ),
        Help.Command(('wyłącz', 'wylacz'), (),
                     'Wyłącza powiadomienia o dzisiejszych urodzinach.'),
    )

    NOTIFICATIONS_HELP = Help(NOTIFICATIONS_COMMANDS,
                              '🎂',
                              group=NOTIFICATIONS_GROUP)
    NOTIFICATIONS_TIME_PRESENTATION = f'{NOTIFICATIONS_TIME[0]}:{str(NOTIFICATIONS_TIME[1]).zfill(2)}'
    NOTIFICATIONS_EXPLANATION = (
        f'Wiadomości z życzeniami wysyłane są o {NOTIFICATIONS_TIME_PRESENTATION} dla członków serwera, '
        'którzy obchodzą tego dnia urodziny i upublicznili tu ich datę.')

    def __init__(self, bot: Somsiad):
        self.bot = bot
        self.already_notified = defaultdict(set)
        self.notification_cycle.start()

    def cog_unload(self):
        self.notification_cycle.cancel()

    @staticmethod
    def _comprehend_date(date_string: str, formats: Sequence[str]) -> dt.date:
        date_string = date_string.replace('-', ' ').replace('.', ' ').replace(
            '/', ' ').strip()
        for i in itertools.count():
            try:
                date = dt.datetime.strptime(date_string, formats[i]).date()
            except ValueError:
                continue
            except IndexError:
                raise ValueError
            else:
                return date

    def comprehend_date_with_year(self, date_string: str) -> dt.date:
        return self._comprehend_date(date_string, self.DATE_WITH_YEAR_FORMATS)

    def comprehend_date_without_year(self, date_string: str) -> dt.date:
        return self._comprehend_date(date_string,
                                     self.DATE_WITHOUT_YEAR_FORMATS)

    def comprehend_month(self, date_string: str) -> int:
        return self._comprehend_date(date_string, self.MONTH_FORMATS).month

    async def send_server_birthday_notifications(
            self, birthday_notifier: BirthdayNotifier):
        wishes = birthday_notifier.WISHES.copy()
        random.shuffle(wishes)
        for birthday_today, wish in zip(birthday_notifier.birthdays_today(),
                                        itertools.cycle(wishes)):
            channel = birthday_notifier.discord_channel(self.bot)
            if channel is None:
                return
            key = f'{channel.guild.id}-{dt.date.today()}'
            if (channel.guild.get_member(birthday_today.user_id) is None
                    or birthday_today.user_id in self.already_notified[key]):
                continue
            self.already_notified[key].add(birthday_today.user_id)
            if birthday_today.age:
                notice = f'{wish} z okazji {birthday_today.age}. urodzin!'
            else:
                notice = f'{wish} z okazji urodzin!'
            try:
                await channel.send(f'<@{birthday_today.user_id}>',
                                   embed=self.bot.generate_embed('🎂', notice))
            except discord.Forbidden:
                try:
                    user = self.bot.get_user(birthday_today.user_id)
                    if user is not None:
                        await self.bot.get_user(birthday_today.user_id).send(
                            f'<@{birthday_today.user_id}>',
                            embed=self.bot.generate_embed(
                                '🎂',
                                notice,
                                f'Miałem wysłać tę wiadomość na kanale #{channel} serwera {channel.guild}, ale nie mam tam do tego uprawnień. Jeśli chcesz, możesz powiadomić o tej sprawie administratora tamtego serwera.',
                            ),
                        )
                except discord.Forbidden:
                    pass

    async def send_all_birthday_today_notifications(self):
        with data.session(commit=True) as session:
            birthday_notifiers = session.query(BirthdayNotifier).all()
            for birthday_notifier in birthday_notifiers:
                if birthday_notifier.channel_id is not None:
                    await self.send_server_birthday_notifications(
                        birthday_notifier)

    @tasks.loop(hours=24)
    async def notification_cycle(self):
        await self.send_all_birthday_today_notifications()

    @notification_cycle.before_loop
    async def before_notification_cycle(self):
        now = dt.datetime.now().astimezone()
        next_iteration_moment = dt.datetime(
            now.year, now.month, now.day,
            *self.NOTIFICATIONS_TIME).astimezone()
        if next_iteration_moment != now:
            if next_iteration_moment < now:
                next_iteration_moment += dt.timedelta(1)
            await discord.utils.sleep_until(next_iteration_moment)

    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

    @commands.group(aliases=['urodziny'],
                    invoke_without_command=True,
                    case_insensitive=True)
    @cooldown()
    async def birthday(self, ctx, *, member: discord.Member = None):
        if member is None:
            await self.bot.send(ctx, embed=self.HELP.embeds)
        else:
            await ctx.invoke(self.birthday_when, member=member)

    @birthday.error
    async def birthday_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            embed = self.bot.generate_embed(
                '⚠️', 'Nie znaleziono na serwerze pasującego użytkownika')
            await self.bot.send(ctx, embed=embed)

    @birthday.command(aliases=['zapamiętaj', 'zapamietaj', 'ustaw'])
    @cooldown()
    async def birthday_remember(self, ctx, *, raw_date_string):
        try:
            date = self.comprehend_date_without_year(raw_date_string)
        except ValueError:
            try:
                date = self.comprehend_date_with_year(raw_date_string)
            except ValueError:
                raise commands.BadArgument('could not comprehend date')
            else:
                if date.year <= BornPerson.EDGE_YEAR:
                    raise commands.BadArgument('date is in too distant past')
                elif date > dt.date.today():
                    raise commands.BadArgument('date is in the future')
        with data.session(commit=True) as session:
            born_person = session.query(BornPerson).get(ctx.author.id)
            if born_person is not None:
                born_person.birthday = date
            else:
                born_person = BornPerson(user_id=ctx.author.id, birthday=date)
                session.add(born_person)
            if ctx.guild is not None:
                this_server = session.query(data.Server).get(ctx.guild.id)
                born_person.birthday_public_servers.append(this_server)
            date_presentation = date.strftime(
                '%-d %B' if date.year == BornPerson.EDGE_YEAR else '%-d %B %Y')
            birthday_public_servers_presentation = ' '.join(
                filter(
                    None,
                    self._get_birthday_public_servers_presentation(
                        born_person,
                        on_server_id=ctx.guild.id if ctx.guild else None),
                ))
            embed = self.bot.generate_embed(
                '✅',
                f'Zapamiętano twoją datę urodzin jako {date_presentation}',
                birthday_public_servers_presentation)
        await self.bot.send(ctx, embed=embed)

    @birthday_remember.error
    async def birthday_remember_error(self, ctx, error):
        notice = None
        if isinstance(error, commands.MissingRequiredArgument):
            notice = 'Nie podano daty'
        elif isinstance(error, commands.BadArgument):
            if str(error) == 'could not comprehend date':
                notice = 'Nie rozpoznano formatu daty'
            elif str(error) == 'date is in too distant past':
                notice = 'Podaj współczesną datę urodzin'
            elif str(error) == 'date is in the future':
                notice = 'Podaj przeszłą datę urodzin'
        if notice is not None:
            embed = self.bot.generate_embed('⚠️', notice)
            await self.bot.send(ctx, embed=embed)

    @birthday.command(aliases=['zapomnij'])
    @cooldown()
    async def birthday_forget(self, ctx):
        forgotten = False
        with data.session(commit=True) as session:
            born_person = session.query(BornPerson).get(ctx.author.id)
            if born_person is not None:
                if born_person.birthday is not None:
                    forgotten = True
                born_person.birthday = None
            else:
                born_person = BornPerson(user_id=ctx.author.id)
                session.add(born_person)
        if forgotten:
            embed = self.bot.generate_embed('✅',
                                            'Zapomniano twoją datę urodzin')
        else:
            embed = self.bot.generate_embed(
                'ℹ️', 'Brak daty urodzin do zapomnienia')
        await self.bot.send(ctx, embed=embed)

    @birthday.command(aliases=['upublicznij'])
    @cooldown()
    @commands.guild_only()
    async def birthday_make_public(self, ctx):
        with data.session(commit=True) as session:
            born_person = session.query(BornPerson).get(ctx.author.id)
            this_server = session.query(data.Server).get(ctx.guild.id)
            if born_person is None or born_person.birthday is None:
                embed = self.bot.generate_embed(
                    'ℹ️',
                    'Nie ustawiłeś swojej daty urodzin, więc nie ma czego upubliczniać'
                )
            elif this_server in born_person.birthday_public_servers:
                birthday_public_servers_presentation = ' '.join(
                    filter(
                        None,
                        self._get_birthday_public_servers_presentation(
                            born_person, on_server_id=ctx.guild.id)))
                embed = self.bot.generate_embed(
                    'ℹ️',
                    'Twoja data urodzin już jest publiczna na tym serwerze',
                    birthday_public_servers_presentation)
            else:
                born_person.birthday_public_servers.append(this_server)
                birthday_public_servers_presentation = ' '.join(
                    filter(
                        None,
                        self._get_birthday_public_servers_presentation(
                            born_person, on_server_id=ctx.guild.id)))
                embed = self.bot.generate_embed(
                    '📖', 'Upubliczniono twoją datę urodzin na tym serwerze',
                    birthday_public_servers_presentation)
        await self.bot.send(ctx, embed=embed)

    @birthday.command(aliases=['utajnij'])
    @cooldown()
    @commands.guild_only()
    async def birthday_make_secret(self, ctx):
        with data.session(commit=True) as session:
            born_person = session.query(BornPerson).get(ctx.author.id)
            this_server = session.query(data.Server).get(ctx.guild.id)
            if born_person is None or born_person.birthday is None:
                embed = self.bot.generate_embed(
                    'ℹ️',
                    'Nie ustawiłeś swojej daty urodzin, więc nie ma czego utajniać'
                )
            elif this_server not in born_person.birthday_public_servers:
                birthday_public_servers_presentation = ' '.join(
                    filter(
                        None,
                        self._get_birthday_public_servers_presentation(
                            born_person, on_server_id=ctx.guild.id)))
                embed = self.bot.generate_embed(
                    'ℹ️', 'Twoja data urodzin już jest tajna na tym serwerze',
                    birthday_public_servers_presentation)
            else:
                born_person.birthday_public_servers.remove(this_server)
                birthday_public_servers_presentation = ' '.join(
                    filter(
                        None,
                        self._get_birthday_public_servers_presentation(
                            born_person, on_server_id=ctx.guild.id)))
                embed = self.bot.generate_embed(
                    '🕵️‍♂️', 'Utajniono twoją datę urodzin na tym serwerze',
                    birthday_public_servers_presentation)
        await self.bot.send(ctx, embed=embed)

    @birthday.command(aliases=['gdzie'])
    @cooldown()
    async def birthday_where(self, ctx):
        with data.session() as session:
            born_person = session.query(BornPerson).get(ctx.author.id)
            birthday_public_servers_presentation, extra = (
                self._get_birthday_public_servers_presentation(
                    born_person,
                    on_server_id=ctx.guild.id if ctx.guild else None,
                    period=False) if born_person is not None else
                ('Nie ustawiłeś swojej daty urodzin, więc nie ma czego upubliczniać',
                 None))
        embed = discord.Embed(
            title=
            f':information_source: {birthday_public_servers_presentation}',
            description=extra,
            color=self.bot.COLOR,
        )
        await self.bot.send(ctx, embed=embed)

    @birthday.command(
        aliases=['lista', 'serwer', 'wszyscy', 'wszystkie', 'kalendarz'])
    @cooldown()
    async def birthday_list(self, ctx):
        with data.session() as session:
            born_persons: List[BornPerson] = (session.query(BornPerson).join(
                BornPerson.birthday_public_servers).filter_by(
                    id=ctx.guild.id).all())
            if not born_persons:
                embed = discord.Embed(
                    title=
                    f':question: Żaden użytkownik nie upublicznił na tym serwerze daty urodzin',
                    color=self.bot.COLOR,
                )
            else:
                embed = discord.Embed(
                    title=
                    f'🗓 {word_number_form(len(born_persons), "użytkownik upublicznił", "użytkownicy upublicznili", "użytkowników upubliczniło")} na tym serwerze swoją datę urodzin',
                    color=self.bot.COLOR,
                )
                born_persons_filtered_sorted = sorted(
                    (person for person in born_persons if person.birthday),
                    key=lambda person: person.birthday.strftime('%m-%d-%Y'),
                )
                for born_person in born_persons_filtered_sorted:
                    user = born_person.discord_user(bot=ctx.bot)
                    date_presentation = born_person.birthday.strftime(
                        '%-d %B' if born_person.birthday.year ==
                        BornPerson.EDGE_YEAR else '%-d %B %Y')
                    embed.add_field(name=str(user),
                                    value=date_presentation,
                                    inline=True)
        return await self.bot.send(ctx, embed=embed)

    @birthday.command(
        aliases=['kiedy', 'sprawdź', 'sprawdz', 'pokaż', 'pokaz'])
    @cooldown()
    async def birthday_when(self, ctx, *, member: discord.Member = None):
        member = member or ctx.author
        with data.session() as session:
            born_person = session.query(BornPerson).get(member.id)
            if born_person is None or born_person.birthday is None:
                if member == ctx.author:
                    address = 'Nie ustawiłeś'
                else:
                    address = f'{member} nie ustawił'
                embed = discord.Embed(
                    title=f':question: {address} swojej daty urodzin',
                    description=
                    'Ustawienia można dokonać komendą `urodziny ustaw`.',
                    color=self.bot.COLOR,
                )
            elif not born_person.is_birthday_public(session, ctx.guild):
                if member == ctx.author:
                    address = 'Nie upubliczniłeś'
                else:
                    address = f'{member} nie upublicznił'
                embed = discord.Embed(
                    title=
                    f':question: {address} na tym serwerze swojej daty urodzin',
                    description=
                    'Upublicznienia można dokonać komendą `urodziny upublicznij`.',
                    color=self.bot.COLOR,
                )
            else:
                emoji = 'birthday' if born_person.is_birthday_today(
                ) else 'calendar_spiral'
                address = 'Urodziłeś' if member == ctx.author else f'{member} urodził'
                date_presentation = born_person.birthday.strftime(
                    '%-d %B' if born_person.birthday.year ==
                    BornPerson.EDGE_YEAR else '%-d %B %Y')
                embed = discord.Embed(
                    title=f':{emoji}: {address} się {date_presentation}',
                    color=self.bot.COLOR)
        return await self.bot.send(ctx, embed=embed)

    @birthday_when.error
    async def birthday_when_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            embed = discord.Embed(
                title=
                ':warning: Nie znaleziono na serwerze pasującego użytkownika!',
                color=self.bot.COLOR)
            await self.bot.send(ctx, embed=embed)

    @birthday.command(aliases=['wiek'])
    @cooldown()
    async def birthday_age(self, ctx, *, member: discord.Member = None):
        member = member or ctx.author
        with data.session() as session:
            born_person = session.query(BornPerson).get(member.id)
            if born_person is None or born_person.birthday is None:
                age = None
                is_birthday_unset = True
            else:
                age = born_person.age() if born_person is not None else None
                is_birthday_unset = False
            if is_birthday_unset:
                if member == ctx.author:
                    address = 'Nie ustawiłeś'
                else:
                    address = f'{member} nie ustawił'
                embed = discord.Embed(
                    title=f':question: {address} swojej daty urodzin',
                    description=
                    'Ustawienia można dokonać komendą `urodziny ustaw`.',
                    color=self.bot.COLOR,
                )
            elif not born_person.is_birthday_public(session, ctx.guild):
                if member == ctx.author:
                    address = 'Nie upubliczniłeś'
                else:
                    address = f'{member} nie upublicznił'
                embed = discord.Embed(
                    title=
                    f':question: {address} na tym serwerze swojej daty urodzin',
                    description=
                    'Upublicznienia można dokonać komendą `urodziny upublicznij`.',
                    color=self.bot.COLOR,
                )
            elif age is None:
                address = 'Nie ustawiłeś' if member == ctx.author else f'{member} nie ustawił'
                embed = discord.Embed(
                    title=f':question: {address} swojego roku urodzenia',
                    description=
                    'Ustawienia można dokonać komendą `urodziny ustaw`.',
                    color=self.bot.COLOR,
                )
            else:
                emoji = 'birthday' if born_person.is_birthday_today(
                ) else 'calendar_spiral'
                address = 'Masz' if member == ctx.author else f'{member} ma'
                embed = discord.Embed(
                    title=
                    f':{emoji}: {address} {word_number_form(age, "rok", "lata", "lat")}',
                    color=self.bot.COLOR)
        return await self.bot.send(ctx, embed=embed)

    @birthday_age.error
    async def birthday_age_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            embed = discord.Embed(
                title=
                ':warning: Nie znaleziono na serwerze pasującego użytkownika!',
                color=self.bot.COLOR)
            await self.bot.send(ctx, embed=embed)

    @birthday.group(aliases=['powiadomienia'],
                    invoke_without_command=True,
                    case_insensitive=True)
    @cooldown()
    @commands.guild_only()
    @has_permissions(administrator=True)
    async def birthday_notifications(self, ctx):
        await self.bot.send(ctx, embed=self.NOTIFICATIONS_HELP.embeds)

    @birthday_notifications.command(aliases=['status'])
    @cooldown()
    @commands.guild_only()
    async def birthday_notifications_status(self,
                                            ctx,
                                            *,
                                            channel: discord.TextChannel = None
                                            ):
        channel = channel or ctx.channel
        with data.session() as session:
            birthday_notifier = session.query(BirthdayNotifier).get(
                ctx.guild.id)
            if birthday_notifier is not None and birthday_notifier.channel_id is not None:
                title = f'✅ Powiadomienia o urodzinach są włączone na #{birthday_notifier.discord_channel(self.bot)}'
                description = self.NOTIFICATIONS_EXPLANATION
            else:
                title = f'🔴 Powiadomienia o urodzinach są wyłączone'
                description = None
        embed = discord.Embed(title=title,
                              description=description,
                              color=self.bot.COLOR)
        await self.bot.send(ctx, embed=embed)

    @birthday_notifications.command(aliases=['włącz', 'wlacz'])
    @cooldown()
    @commands.guild_only()
    async def birthday_notifications_enable(self,
                                            ctx,
                                            *,
                                            channel: discord.TextChannel = None
                                            ):
        channel = channel or ctx.channel
        with data.session(commit=True) as session:
            birthday_notifier = session.query(BirthdayNotifier).get(
                ctx.guild.id)
            title = f'✅ Włączono powiadomienia o urodzinach na #{channel}'
            if birthday_notifier is not None:
                if birthday_notifier.channel_id != channel.id:
                    birthday_notifier.channel_id = channel.id
                else:
                    title = f'ℹ️ Powiadomienia o urodzinach już są włączone na #{channel}'
            else:
                birthday_notifier = BirthdayNotifier(server_id=ctx.guild.id,
                                                     channel_id=channel.id)
                session.add(birthday_notifier)
        embed = discord.Embed(title=title,
                              description=self.NOTIFICATIONS_EXPLANATION,
                              color=self.bot.COLOR)
        await self.bot.send(ctx, embed=embed)

    @birthday_notifications.command(aliases=['wyłącz', 'wylacz'])
    @cooldown()
    @commands.guild_only()
    async def birthday_notifications_disable(self, ctx):
        with data.session(commit=True) as session:
            birthday_notifier = session.query(BirthdayNotifier).get(
                ctx.guild.id)
            title = 'ℹ️ Powiadomienia o urodzinach już są wyłączone'
            if birthday_notifier is not None:
                if birthday_notifier.channel_id is not None:
                    birthday_notifier.channel_id = None
                    title = '🔴 Wyłączono powiadomienia o urodzinach'
            else:
                birthday_notifier = BirthdayNotifier(server_id=ctx.guild.id)
                session.add(birthday_notifier)
        embed = discord.Embed(title=title, color=self.bot.COLOR)
        await self.bot.send(ctx, embed=embed)
예제 #6
0
class Pins(commands.Cog):
    GROUP = Help.Command(
        ('przypięte', 'przypinki', 'piny'), (),
        'Komendy związane z archiwizacją przypiętych wiadomości.')
    COMMANDS = (Help.Command(
        ('kanał', 'kanal'), '?kanał',
        'Jeśli podano <?kanał>, ustawia go jako serwerowy kanał archiwum przypiętych wiadomości. '
        'W przeciwnym razie pokazuje jaki kanał obecnie jest archiwum przypiętych wiadomości.'
    ),
                Help.Command(('archiwizuj', 'zarchiwizuj'), (
                ), 'Archiwizuje wiadomości przypięte na kanale na którym użyto komendy przez zapisanie ich na kanale archiwum.'
                             ),
                Help.Command(('wyczyść', 'wyczysc'), (),
                             'Odpina wszystkie wiadomości na kanale.'))
    HELP = Help(COMMANDS, '📌', group=GROUP)

    def __init__(self, bot: commands.Bot):
        self.bot = bot

    @commands.group(aliases=['przypięte', 'przypinki', 'piny'],
                    invoke_without_command=True,
                    case_insensitive=True)
    @cooldown()
    async def pins(self, ctx):
        """A group of pin-related commands."""
        await self.bot.send(ctx, embeds=self.HELP.embeds)

    @pins.command(aliases=['kanał', 'kanal'])
    @cooldown()
    @commands.guild_only()
    @commands.has_permissions(manage_channels=True)
    async def pins_channel(self, ctx, channel: discord.TextChannel = None):
        """Sets the pin archive channel of the server."""
        session = data.Session()
        pin_archive = session.query(PinArchive).get(ctx.guild.id)
        if channel is not None:
            if pin_archive:
                pin_archive.channel_id = channel.id
            else:
                pin_archive = PinArchive(server_id=ctx.guild.id,
                                         channel_id=channel.id)
                session.add(pin_archive)
            session.commit()
            session.close()
            embed = self.bot.generate_embed(
                '✅',
                f'Ustawiono #{channel} jako kanał archiwum przypiętych wiadomości'
            )
        else:
            if pin_archive is not None and pin_archive.channel_id is not None:
                notice = f'Kanałem archiwum przypiętych wiadomości jest #{pin_archive.discord_channel(self.bot)}'
            else:
                notice = 'Nie ustawiono na serwerze kanału archiwum przypiętych wiadomości'
            embed = self.bot.generate_embed('🗃️', notice)
        await self.bot.send(ctx, embed=embed)

    @pins_channel.error
    async def pins_channel_error(self, ctx, error):
        notice = None
        if isinstance(error, commands.BadArgument):
            notice = 'Nie znaleziono podanego kanału na serwerze'
        elif isinstance(error, commands.MissingPermissions):
            notice = (
                'Do sprawdzenia lub zmiany kanału archiwum przypiętych wiadomości potrzebne są '
                'uprawnienia do zarządzania kanałami')
        if notice is not None:
            embed = self.bot.generate_embed('⚠️', notice)
            await self.bot.send(ctx, embed=embed)

    @pins.command(aliases=['archiwizuj', 'zarchiwizuj'])
    @cooldown()
    @commands.guild_only()
    @commands.has_permissions(manage_messages=True)
    async def pins_archive(self, ctx):
        """Archives pins in the channel where the command was invoked."""
        async with ctx.typing():
            with data.session() as session:
                pin_archive = session.query(PinArchive).get(ctx.guild.id)
                if pin_archive is None or pin_archive.channel_id is None:
                    emoji, notice = '⚠️', 'Nie ustawiono na serwerze kanału archiwum przypiętych wiadomości'
                elif pin_archive.discord_channel(self.bot) is None:
                    emoji, notice = '⚠️', 'Ustawiony kanał archiwum przypiętych wiadomości już nie istnieje'
                elif channel_being_processed_for_servers.get(
                        ctx.guild.id) is not None:
                    emoji, notice = (
                        '🔴', 'Na serwerze właśnie trwa przetwarzanie kanału '
                        f'#{channel_being_processed_for_servers[ctx.guild.id]}'
                    )
                else:
                    channel_being_processed_for_servers[
                        ctx.guild.id] = pin_archive.discord_channel(self.bot)
                    try:
                        try:
                            async with channel_being_processed_for_servers[
                                    ctx.guild.id].typing():
                                archived = await pin_archive.archive(
                                    self.bot, ctx.channel)
                        except ValueError:
                            emoji, notice = '🔴', 'Brak przypiętych wiadomości do zarchiwizowania'
                        else:
                            forms = ('przypiętą wiadomość',
                                     'przypięte wiadomości',
                                     'przypiętych wiadomości')
                            emoji, notice = '✅', f'Zarchiwizowano {word_number_form(archived, *forms)}'
                    except:
                        raise
                    finally:
                        channel_being_processed_for_servers[
                            ctx.guild.id] = None
            embed = self.bot.generate_embed(emoji, notice)
            await self.bot.send(ctx, embed=embed)

    @pins.command(aliases=['wyczyść', 'wyczysc'])
    @cooldown()
    @commands.guild_only()
    @commands.has_permissions(manage_messages=True)
    async def pins_clear(self, ctx):
        """Unpins all pins in the channel."""
        async with ctx.typing():
            messages = await ctx.channel.pins()
            if not messages:
                emoji, notice = '🔴', 'Brak przypiętych wiadomości do odpięcia'
            elif channel_being_processed_for_servers.get(
                    ctx.guild.id) == ctx.channel:
                emoji, notice = '🔴', 'Ten kanał jest właśnie przetwarzany'
            else:
                channel_being_processed_for_servers[ctx.guild.id] = ctx.channel
                try:
                    for pin in messages:
                        await pin.unpin()
                except:
                    raise
                else:
                    forms = ('przypiętą wiadomość', 'przypięte wiadomości',
                             'przypiętych wiadomości')
                    emoji, notice = '✅', f'Odpięto {word_number_form(len(messages), *forms)}'
                finally:
                    channel_being_processed_for_servers[ctx.guild.id] = None
            embed = self.bot.generate_embed(emoji, notice)
            await self.bot.send(ctx, embed=embed)
예제 #7
0
class Help(commands.Cog):
    COMMANDS = (
        _Help.Command(('pomocy', 'pomoc', 'help'), (),
                      'Wysyła ci tę wiadomość.'),
        _Help.Command(('8-ball', '8ball', 'eightball', '8', 'czy'), 'pytanie',
                      'Zadaje <pytanie> magicznej kuli.'),
        _Help.Command(('wybierz', ), (
            'opcje',
        ), 'Wybiera opcję z oddzielonych przecinkami, średnikami, "lub", "albo" lub "czy" <opcji>.'
                      ),
        _Help.Command(('rzuć', 'rzuc'),
                      ('?liczba kości', '?liczba ścianek kości'),
                      'Rzuca kością/koścmi.'),
        _Help.Command(
            ('google', 'gugiel', 'g'), 'zapytanie',
            'Wysyła <zapytanie> do [Google](https://www.google.com) i zwraca najlepiej pasującą stronę.'
        ),
        _Help.Command(
            ('googleimage', 'gi', 'i'), 'zapytanie',
            'Wysyła <zapytanie> do [Google](https://www.google.pl/imghp) i zwraca najlepiej pasujący obrazek.'
        ),
        _Help.Command(
            ('youtube', 'yt', 'tuba'), 'zapytanie',
            'Zwraca z [YouTube](https://www.youtube.com) najlepiej pasujące do <zapytania> wideo.'
        ),
        _Help.Command(('wikipedia', 'wiki', 'w'), (
            'dwuliterowy kod języka',
            'hasło'
        ), 'Sprawdza znaczenie <hasła> w danej wersji językowej [Wikipedii](https://www.wikipedia.org/).'
                      ),
        _Help.Command(
            'tmdb', 'zapytanie/podkomenda',
            'Zwraca z [TMDb](https://www.themoviedb.org/) najlepiej pasujący do <?zapytania> film/serial/osobę. '
            'Użyj bez <?zapytania/podkomendy>, by dowiedzieć się więcej.'),
        _Help.Command(
            ('tłumacz', 'tlumacz', 'translator'),
            ('kod języka źródłowego', 'kod języka docelowego', 'tekst'),
            'Tłumaczy tekst z [Yandex](https://translate.yandex.com/). '
            'Wpisanie znaku zapytania w miejscu kodu języka źródłowego spowoduje wykrycie języka źródłowego.'
        ),
        _Help.Command(
            'spotify', '?użytkownik Discorda',
            'Zwraca informacje na temat utworu obecnie słuchanego przez <?użytkownika Discorda> na Spotify. '
            'Jeśli nie podano <?użytkownika Discorda>, przyjmuje ciebie.'),
        _Help.Command(
            ('lastfm', 'last', 'fm', 'lfm'), 'użytkownik Last.fm',
            'Zwraca z Last.fm informacje na temat utworu obecnie słuchanego przez <użytkownika Last.fm>.'
        ),
        _Help.Command(
            ('goodreads', 'gr', 'książka', 'ksiazka'), 'tytuł/autor',
            'Zwraca z [goodreads](https://www.goodreads.com) informacje na temat książki najlepiej pasującej do '
            '<tytułu/autora>.'),
        _Help.Command(
            ('urbandictionary', 'urban'), 'wyrażenie',
            'Sprawdza znaczenie <wyrażenia> w [Urban Dictionary](https://www.urbandictionary.com).'
        ),
        _Help.Command(
            ('wolframalpha', 'wolfram', 'wa', 'kalkulator', 'oblicz', 'policz',
             'przelicz', 'konwertuj'), ('zapytanie', ),
            '[Wolfram Alpha](https://www.wolframalpha.com/) – oblicza, przelicza, podaje najróżniejsze informacje. '
            'Usługa po angielsku.'),
        _Help.Command(('isitup', 'isup', 'czydziała', 'czydziala'), 'link',
                      'Sprawdza status danej strony.'),
        _Help.Command(
            ('rokszkolny', 'wakacje', 'ilejeszcze'), '?podkomenda',
            'Zwraca ile jeszcze zostało do końca roku szkolnego lub wakacji. '
            'Użyj z <podkomendą> "matura", by dowiedzieć się ile zostało do matury.'
        ),
        _Help.Command(('subreddit', 'sub', 'r'), 'subreddit',
                      'Zwraca informacje o <subreddicie>.'),
        _Help.Command(('user', 'u'), 'użytkownik Reddita',
                      'Zwraca informacje o <użytkowniku Reddita>.'),
        _Help.Command(
            ('disco', 'd'),
            '?podkomenda',
            'Komendy związane z odtwarzaniem muzyki. Użyj bez <?podkomendy>, by dowiedzieć się więcej.',
        ),
        _Help.Command(
            'role', (),
            'Wyświetla wszystkie role na serwerze wraz z liczbą członków je mających.'
        ),
        _Help.Command(
            ('stat', 'staty', 'aktywnosć', 'aktywnosc'),
            '?użytkownik/kanał/kategoria/podkomenda',
            'Komendy związane ze statystykami serwerowymi. '
            'Użyj bez <?użytkownika/kanału/kategorii/podkomendy>, by dowiedzieć się więcej.',
        ),
        _Help.Command(
            'urodziny',
            '?podkomenda/użytkownik',
            'Komendy związane z datami urodzin. Użyj bez <?podkomendy/użytkownika>, by dowiedzieć się więcej.',
        ),
        _Help.Command(
            ('handlowe', 'niedzielehandlowe'),
            '?podkomenda',
            'Komendy związane z niedzielami handlowymi. Użyj bez <?podkomendy>, by dowiedzieć się więcej.',
        ),
        _Help.Command(('przypomnij', 'przypomnienie', 'pomidor'),
                      ('liczba minut/data i godzina', 'treść'),
                      'Przypomina o <treści> po upływie podanego czasu.'),
        _Help.Command(('spal', 'burn'), ('liczba minut/data i godzina',
                                         '?treść – może to też być załącznik'),
                      'Usuwa wiadomość po upływie podanego czasu.'),
        _Help.Command(
            ('kolory', 'kolor', 'kolorki', 'kolorek'),
            '?podkomenda',
            'Komendy związane z kolorami nicków samodzielnie wybieranymi przez użytkowników. '
            'Użyj bez <?podkomendy>, by dowiedzieć się więcej.',
        ),
        _Help.Command(
            'przypinki',
            '?podkomenda',
            'Komendy związane z archiwizacją przypiętych wiadomości. '
            'Użyj bez <?podkomendy>, by dowiedzieć się więcej.',
        ),
        _Help.Command(
            ('głosowanie', 'glosowanie'),
            ('?liczba minut/data i godzina', 'sprawa'),
            'Przeprowadza głosowanie za/przeciw dotyczące <sprawy>. '
            'Ogłasza wynik po upływie podanego czasu, jeśli go podano.'),
        _Help.Command(('pomógł', 'pomogl'), '?użytkownik Discorda',
                      'Oznacza pomocną wiadomość za pomocą reakcji.'),
        _Help.Command(('niepomógł', 'niepomogl'), '?użytkownik Discorda',
                      'Oznacza niepomocną wiadomość za pomocą reakcji.'),
        _Help.Command(('hm', 'hmm', 'hmmm', 'hmmmm', 'hmmmmm', 'myśl', 'mysl',
                       'think', 'thinking', '🤔'), '?użytkownik Discorda', '🤔'),
        _Help.Command(('^', 'to', 'this', 'up', 'upvote'),
                      '?użytkownik Discorda', '⬆'),
        _Help.Command('f', '?użytkownik Discorda', 'F'),
        _Help.Command(
            ('zareaguj', 'reaguj', 'x'), ('?użytkownik Discorda', 'reakcje'),
            'Dodaje <reakcje> do ostatniej wiadomości wysłanej na kanale '
            '(jeśli podano <?użytkownika Discorda>, to ostatnią jego autorstwa na kanale).'
        ),
        _Help.Command('oof', (), 'Oof!'),
        _Help.Command(
            'oof ile', '?użytkownik Discorda',
            'Zlicza oofnięcia dla <?użytkownika Discorda> lub, jeśli nie podano <?użytkownika Discorda>, dla ciebie. '
        ),
        _Help.Command(
            'oof serwer', (),
            'Zlicza oofnięcia na serwerze i generuje ranking ooferów.'),
        _Help.Command(('obróć', 'obroc', 'niewytrzymie'), (
            '?użytkownik', '?stopni/razy'
        ), 'Obraca ostatni załączony na kanale lub, jeśli podano <?użytkownika>, na kanale przez <?użytkownika> obrazek <?stopni/razy> (domyślnie 90 stopni/1 raz) zgodnie z ruchem wskazówek zegara.'
                      ),
        _Help.Command(
            ('deepfry', 'usmaż', 'głębokosmaż', 'usmaz', 'glebokosmaz'),
            ('?użytkownik', '?poziom usmażenia'),
            'Smaży ostatni załączony na kanale lub, jeśli podano <?użytkownika>, na kanale przez <?użytkownika> obrazek <?poziom usmażenia> razy (domyślnie 2 razy). '
        ),
        _Help.Command(
            ('robot9000', 'r9k', 'było', 'bylo', 'byo'), '?użytkownik',
            'Sprawdza czy ostatnio załączony na kanale lub, jeśli podano <?użytkownika>, na kanale przez <?użytkownika> obrazek pojawił się wcześniej na serwerze.'
        ),
        _Help.Command('tableflip', (), '(╯°□°)╯︵ ┻━┻'),
        _Help.Command('unflip', (), '┬─┬ ノ( ゜-゜ノ)'),
        _Help.Command('shrug', (), r'¯\_(ツ)_/¯'),
        _Help.Command(('lenny', 'lennyface'), (), '( ͡° ͜ʖ ͡°)'),
        _Help.Command(('lenno', 'lennoface'), (), '( ͡ʘ ͜ʖ ͡ʘ)'),
        _Help.Command(('dej', 'gib'), '?rzecz', '༼ つ ◕_◕ ༽つ <?rzecz>'),
        _Help.Command(('nie', 'nope', 'no'), (
        ), 'Usuwa ostatnią wiadomość wysłaną przez bota na kanale jako rezultat użytej przez ciebie komendy.'
                      ),
        _Help.Command(
            ('warn', 'ostrzeż', 'ostrzez'), ('użytkownik Discorda', 'powód'),
            'Ostrzega <użytkownika Discorda>. '
            'Działa tylko dla członków serwera mających uprawnienia do wyrzucania innych.'
        ),
        _Help.Command(
            ('kick', 'wyrzuć', 'wyrzuc'), ('użytkownik Discorda', 'powód'),
            'Wyrzuca <użytkownika Discorda>. '
            'Działa tylko dla członków serwera mających uprawnienia do wyrzucania innych.'
        ),
        _Help.Command(('ban', 'zbanuj'), (
            'użytkownik Discorda',
            'powód'
        ), 'Banuje <użytkownika Discorda>. Działa tylko dla członków serwera mających uprawnienia do banowania innych.'
                      ),
        _Help.Command(
            ('przebacz', 'pardon'), ('użytkownik Discorda'),
            'Usuwa wszystkie ostrzeżenia <użytkownika Discorda> na serwerze. '
            'Działa tylko dla członków serwera mających uprawnienia administratora.'
        ),
        _Help.Command(
            'kartoteka', ('?użytkownik Discorda', '?typy zdarzeń'),
            'Sprawdza kartotekę <?użytkownika Discorda> pod kątem <?typów zdarzeń>. '
            'Jeśli nie podano <?użytkownika Discorda>, przyjmuje ciebie. '
            'Jeśli nie podano typu <?typów zdarzeń>, zwraca wszystkie zdarzenia.'
        ),
        _Help.Command(
            ('wyczyść', 'wyczysc'), '?liczba',
            'Usuwa <?liczbę> ostatnich wiadomości z kanału lub, jeśli nie podano <?liczby>, jedną ostatnią wiadomość '
            'z kanału na którym użyto komendy. Działa tylko dla członków serwera mających uprawnienia '
            'do zarządzania wiadomościami na kanale.'),
        _Help.Command(
            ('prefiks', 'prefix'), '?podkomenda',
            'Komendy związane z własnymi serwerowymi prefiksami komend. '
            'Użyj bez <?podkomendy>, by dowiedzieć się więcej.'),
        _Help.Command(('ping', 'pińg'), (), 'Pong!'),
        _Help.Command(('wersja', 'v'), (), 'Działająca wersja bota.'),
        _Help.Command(('informacje', 'info'), (),
                      'Działająca wersja bota plus dodatkowe informacje.'),
    )
    DESCRIPTION = (
        'Somsiad jestem. Pomagam ludziom w różnych kwestiach. '
        'By skorzystać z mojej pomocy wystarczy wysłać komendę w miejscu, w którym będę mógł ją zobaczyć. '
        'Lista komend wraz z ich opisami znajduje się poniżej. '
        'Używając ich na serwerach pamiętaj o prefiksie (możesz zawsze sprawdzić go za pomocą '
        f'`{configuration["command_prefix"]}prefiks sprawdź`).\n'
        'W (nawiasach okrągłych) podane są aliasy komend.\n'
        'W <nawiasach ostrokątnych> podane są argumenty komend. Jeśli przed nazwą argumentu jest ?pytajnik, '
        'oznacza to, że jest to argument opcjonalny.')

    def __init__(self, bot: commands.Bot):
        self.bot = bot
        description = self.DESCRIPTION + f'\nBy dowiedzieć się o mnie więcej, wejdź na {self.bot.WEBSITE_URL}.'
        self.HELP = _Help(self.COMMANDS, '👋', 'Dobry!', description)

    @commands.command(aliases=['help', 'pomocy', 'pomoc'])
    @cooldown()
    async def help_message(self, ctx):
        await self.bot.send(ctx, direct=True, embeds=self.HELP.embeds)
예제 #8
0
class TradeSundays(commands.Cog, SomsiadMixin):
    GROUP = Help.Command(('handlowe', 'niedzielehandlowe'), (), 'Komendy związane z niedzielami handlowymi.')
    COMMANDS = (
        Help.Command(
            ('najbliższa', 'najblizsza'),
            (),
            'Zwraca informacje na temat najbliższej niedzieli oraz najbliższej niedzieli handlowej.',
        ),
        Help.Command(
            ('terminarz', 'lista', 'spis'),
            ('?rok', '?miesiąc'),
            'Zwraca terminarz niedziel handlowych w <?roku>, lub, jeśli go nie podano, w bieżącym roku, '
            'z podziałem na miesiące. Jeśli wraz z <?rokiem> podano <?miesiąc>, '
            'uwzględnione zostaną tylko niedziele handlowe w <?miesiącu>.',
        ),
    )
    HELP = Help(COMMANDS, '🛒', group=GROUP)

    @commands.group(
        aliases=['niedzielehandlowe', 'handlowe', 'niedzielahandlowa', 'handlowa'],
        invoke_without_command=True,
        case_insensitive=True,
    )
    @cooldown()
    async def trade_sundays(self, ctx):
        await self.bot.send(ctx, embed=self.HELP.embeds)

    @trade_sundays.command(aliases=['najbliższa', 'najblizsza'])
    @cooldown()
    async def trade_sundays_nearest(self, ctx):
        nearest_sunday_date = determine_nearest_sunday_after_date()
        nearest_trade_sunday_date = determine_nearest_trade_sunday_after_date()
        if nearest_sunday_date == nearest_trade_sunday_date:
            emoji = '🛒'
            description = None
            if dt.date.today() == nearest_sunday_date:
                notice = 'Dzisiejsza niedziela jest handlowa'
            else:
                notice = f'Najbliższa niedziela, {nearest_sunday_date.strftime("%-d %B")}, będzie handlowa'
        else:
            emoji = '🚫'
            description = f'Następna niedziela handlowa to {nearest_trade_sunday_date.strftime("%-d %B")}.'
            if dt.date.today() == nearest_sunday_date:
                notice = 'Dzisiejsza niedziela nie jest handlowa'
            else:
                notice = f'Najbliższa niedziela, {nearest_sunday_date.strftime("%-d %B")}, nie będzie handlowa'
        embed = self.bot.generate_embed(emoji, notice, description)
        await self.bot.send(ctx, embed=embed)

    @trade_sundays.command(aliases=['terminarz', 'lista', 'spis'])
    @cooldown()
    async def trade_sundays_list(self, ctx, year: Optional[int], month: Optional[int]):
        month_names = [
            'Styczeń',
            'Luty',
            'Marzec',
            'Kwiecień',
            'Maj',
            'Czerwiec',
            'Lipiec',
            'Sierpień',
            'Wrzesień',
            'Październik',
            'Listopad',
            'Grudzień',
        ]
        year = year or dt.date.today().year
        if not 1990 <= year <= 9999:
            raise commands.BadArgument('Rok musi być w przedziale od 1990 do 9999 włącznie')
        if month is not None and not 1 <= month <= 12:
            raise commands.BadArgument('Miesiąc musi być w przedziale od 1 do 12 włącznie')
        trade_sunday_dates = determine_trade_sunday_dates(year, month)
        trade_sunday_dates_by_month = [[] for _ in range(12)]
        for trade_sunday_date in trade_sunday_dates:
            trade_sunday_dates_by_month[trade_sunday_date.month - 1].append(str(trade_sunday_date.day))
        embed = self.bot.generate_embed('🗓', f'Niedziele handlowe w {year}')
        for i, i_month in enumerate(trade_sunday_dates_by_month):
            if i_month:
                embed.add_field(name=month_names[i], value=', '.join(i_month))
        await self.bot.send(ctx, embed=embed)

    @trade_sundays_list.error
    async def trade_sundays_list_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            embed = self.bot.generate_embed('⚠️', str(error))
            await self.bot.send(ctx, embed=embed)
예제 #9
0
class Activity(commands.Cog):
    GROUP = Help.Command(
        'stat', (), 'Komendy związane ze statystykami serwerowymi. '
        'Użyj <?użytkownika/kanału/kategorii> zamiast <?podkomendy>, by otrzymać raport statystyczny.'
    )
    COMMANDS = (
        Help.Command('serwer', (), 'Wysyła raport o serwerze.'),
        Help.Command(
            ('kanał', 'kanal'), '?kanał',
            'Wysyła raport o kanale. Jeśli nie podano kanału, przyjmuje kanał na którym użyto komendy.'
        ),
        Help.Command(
            'kategoria', '?kategoria',
            'Wysyła raport o kategorii. Jeśli nie podano kategorii, przyjmuje kategorię do której należy kanał, '
            'na którym użyto komendy.'
        ),
        Help.Command(
            ('użytkownik', 'uzytkownik', 'user'), '?użytkownik',
            'Wysyła raport o użytkowniku. Jeśli nie podano użytkownika, przyjmuje użytkownika, który użył komendy.'
        )
    )
    HELP = Help(COMMANDS, '📈', group=GROUP)

    def __init__(self, bot: commands.Bot):
        self.bot = bot

    @commands.group(
        aliases=['staty', 'stats', 'activity', 'aktywność', 'aktywnosc'], invoke_without_command=True,
        case_insensitive=True
    )
    @cooldown()
    async def stat(
            self, ctx,
            subject: Union[discord.TextChannel, discord.CategoryChannel, discord.Member, discord.User, int] = None,
            last_days: int = None
    ):
        if subject is None:
            await self.bot.send(ctx, embeds=self.HELP.embeds)
        else:
            async with ctx.typing():
                report = Report(ctx, subject, last_days=last_days)
                await report.enqueue()

    @stat.error
    async def stat_error(self, ctx, error):
        if isinstance(error, commands.BadUnionArgument):
            await self.bot.send(ctx, embed=self.bot.generate_embed(
                '⚠️', 'Nie znaleziono na serwerze pasującego użytkownika, kanału ani kategorii'
            ))

    @stat.command(aliases=['server', 'serwer'])
    @cooldown()
    @commands.guild_only()
    async def stat_server(self, ctx, last_days: int = None):
        async with ctx.typing():
            report = Report(ctx, ctx.guild, last_days=last_days)
            await report.enqueue()

    @stat.command(aliases=['channel', 'kanał', 'kanal'])
    @cooldown()
    @commands.guild_only()
    async def stat_channel(self, ctx, channel: discord.TextChannel = None, last_days: int = None):
        channel = channel or ctx.channel
        async with ctx.typing():
            report = Report(ctx, channel, last_days=last_days)
            await report.enqueue()

    @stat_channel.error
    async def stat_channel_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            await self.bot.send(
                ctx, embed=self.bot.generate_embed('⚠️', 'Nie znaleziono na serwerze pasującego kanału')
            )

    @stat.command(aliases=['category', 'kategoria'])
    @cooldown()
    @commands.guild_only()
    async def stat_category(self, ctx, category: discord.CategoryChannel = None, last_days: int = None):
        if category is None and ctx.channel.category_id is None:
            raise commands.BadArgument
        async with ctx.typing():
            report = Report(ctx, category or self.bot.get_channel(ctx.channel.category_id), last_days=last_days)
            await report.enqueue()

    @stat_category.error
    async def stat_category_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            await self.bot.send(
                ctx, embed=self.bot.generate_embed('⚠️', 'Nie znaleziono na serwerze pasującej kategorii')
            )

    @stat.command(aliases=['user', 'member', 'użytkownik', 'członek'])
    @cooldown()
    @commands.guild_only()
    async def stat_member(
            self, ctx, member: Union[discord.Member, discord.User, int] = None, last_days: int = None
    ):
        member = member or ctx.author
        async with ctx.typing():
            report = Report(ctx, member, last_days=last_days)
            await report.enqueue()

    @stat_member.error
    async def stat_member_error(self, ctx, error):
        if isinstance(error, commands.BadUnionArgument):
            await self.bot.send(
                ctx, embed=self.bot.generate_embed('⚠️', 'Nie znaleziono na serwerze pasującego użytkownika')
            )
예제 #10
0
class TradeSundays(commands.Cog):
    GROUP = Help.Command(('handlowe', 'niedzielehandlowe'), (), 'Komendy związane z niedzielami handlowymi.')
    COMMANDS = (
        Help.Command(
            ('najbliższa', 'najblizsza'), (),
            'Zwraca informacje na temat najbliższej niedzieli oraz najbliższej niedzieli handlowej.'
        ),
        Help.Command(
            ('terminarz', 'lista', 'spis'), ('?rok', '?miesiąc'),
            'Zwraca terminarz niedziel handlowych w <?roku>, lub, jeśli go nie podano, w bieżącym roku, '
            'z podziałem na miesiące. Jeśli wraz z <?rokiem> podano <?miesiąc>, '
            'uwzględnione zostaną tylko niedziele handlowe w <?miesiącu>.'
        )
    )
    HELP = Help(COMMANDS, '🛒', group=GROUP)
    ONE_WEEK = dt.timedelta(7)

    def __init__(self, bot: commands.Bot):
        self.bot = bot

    def determine_easter_date(self, year: int) -> dt.date:
        if not isinstance(year, int):
            raise TypeError('year must be an int')
        if year < 1583:
            raise ValueError('year must be 1583 or later')
        a = year % 19
        b, c = divmod(year, 100)
        d, e = divmod(b, 4)
        g = (8 * b + 13) // 25
        h = (19 * a + b - d - g + 15) % 30
        i, k = divmod(c, 4)
        l = (2 * e + 2 * i - h - k + 32) % 7
        m = (a + 11 * h + 19 * l) // 433
        n = (h + l - 7 * m + 90) // 25
        p = (h + l - 7 * m + 33 * n + 19) % 32
        return dt.date(year, n, p)

    def determine_possible_sunday_holiday_dates(self, year: int) -> List[dt.date]:
        if not isinstance(year, int):
            raise TypeError('year must be an int')
        if year < 1990:
            raise ValueError('year must be 1990 or later')
        possible_sunday_holiday_dates = [
            dt.date(year, holiday[0], holiday[1]) for holiday in (
                (1, 1), # Nowy Rok
                (3, 1), # Święto Państwowe
                (3, 3), # Święto Narodowe Trzeciego Maja
                (8, 15), # Wniebowzięcie Najświętszej Maryi Panny,
                (11, 1), # Wszystkich Świętych
                (11, 11), # Narodowe Święto Niepodległości
                (12, 25), # pierwszy dzień Bożego Narodzenia
                (12, 26) # drugi dzień Bożego Narodzenia
            )
        ]
        if year >= 2011:
            possible_sunday_holiday_dates.append(dt.date(year, 1, 6)) # Święto Trzech Króli
        easter_date = self.determine_easter_date(year) # pierwszy dzień Wielkiej Nocy
        pentecost_date = easter_date + dt.timedelta(49) # dzień Bożego Ciała
        possible_sunday_holiday_dates.append(easter_date)
        possible_sunday_holiday_dates.append(pentecost_date)
        return possible_sunday_holiday_dates

    def determine_special_trade_sunday_dates(self, year: int) -> List[dt.date]:
        special_trade_sunday_dates = []
        special_trade_sunday_dates.append(
            self.determine_nearest_sunday_before_date_exclusive(self.determine_easter_date(year))
        )
        special_trade_sunday_dates.append(self.determine_nearest_sunday_before_date_exclusive(dt.date(year, 12, 25)))
        special_trade_sunday_dates.append(special_trade_sunday_dates[1]-self.ONE_WEEK)
        return special_trade_sunday_dates

    def determine_nearest_sunday_before_date_exclusive(self, date: dt.date = None) -> dt.date:
        if date is not None and not isinstance(date, dt.date):
            raise TypeError('date must be None or a datetime.date')
        date = date or dt.date.today()
        return date - dt.timedelta(date.isoweekday())

    def determine_nearest_sunday_after_date_inclusive(self, date: dt.date = None) -> dt.date:
        if date is not None and not isinstance(date, dt.date):
            raise TypeError('date must be None or a datetime.date')
        date = date or dt.date.today()
        return date + dt.timedelta(7-date.isoweekday())

    def _determine_trade_sunday_dates_before_2018(
            self, possible_sunday_holiday_dates: List[dt.date], year: int, month: int
    ) -> List[dt.date]:
        trade_sundays = []
        if month:
            current_sunday_date = self.determine_nearest_sunday_after_date_inclusive(dt.date(year, month, 1))
            while current_sunday_date.month == month:
                if current_sunday_date not in possible_sunday_holiday_dates:
                    trade_sundays.append(current_sunday_date)
                current_sunday_date += self.ONE_WEEK
        else:
            current_sunday_date = self.determine_nearest_sunday_after_date_inclusive(dt.date(year, 1, 1))
            while current_sunday_date.year == year:
                if current_sunday_date not in possible_sunday_holiday_dates:
                    trade_sundays.append(current_sunday_date)
                current_sunday_date += self.ONE_WEEK
        return trade_sundays

    def _determine_trade_sunday_dates_2018(
            self, exceptions: List[dt.date], possible_sunday_holiday_dates: List[dt.date], year: int, month: int
    ) -> List[dt.date]:
        trade_sundays = []
        if month:
            if month < 3:
                current_sunday_date = self.determine_nearest_sunday_after_date_inclusive(dt.date(year, month, 1))
                while current_sunday_date.month == month:
                    if current_sunday_date not in possible_sunday_holiday_dates:
                        trade_sundays.append(current_sunday_date)
                    current_sunday_date += self.ONE_WEEK
            else:
                first_sunday_of_month = self.determine_nearest_sunday_after_date_inclusive(dt.date(year, month, 1))
                if first_sunday_of_month not in exceptions:
                    trade_sundays.append(first_sunday_of_month)
                last_sunday_of_month = self.determine_nearest_sunday_before_date_exclusive(
                    dt.date(year if month < 12 else year + 1, month + 1 if month < 12 else 1, 1)
                )
                if last_sunday_of_month not in exceptions:
                    trade_sundays.append(last_sunday_of_month)
        else:
            current_sunday_date = self.determine_nearest_sunday_after_date_inclusive(dt.date(year, 1, 1))
            while current_sunday_date.month < 3:
                if current_sunday_date not in possible_sunday_holiday_dates:
                    trade_sundays.append(current_sunday_date)
                current_sunday_date += self.ONE_WEEK
            for i_month in range(3, 13):
                first_sunday_of_month = self.determine_nearest_sunday_after_date_inclusive(dt.date(year, i_month, 1))
                if first_sunday_of_month not in exceptions:
                    trade_sundays.append(first_sunday_of_month)
                last_sunday_of_month = self.determine_nearest_sunday_before_date_exclusive(
                    dt.date(year if i_month < 12 else year + 1, i_month + 1 if i_month < 12 else 1, 1)
                )
                if last_sunday_of_month not in exceptions:
                    trade_sundays.append(last_sunday_of_month)
        return trade_sundays

    def _determine_trade_sunday_dates_2019(
            self, exceptions: List[dt.date], year: int, month: int
    ) -> List[dt.date]:
        trade_sundays = []
        if month:
            last_sunday_of_month = self.determine_nearest_sunday_before_date_exclusive(
                dt.date(year if month < 12 else year + 1, month + 1 if month < 12 else 1, 1)
            )
            if last_sunday_of_month not in exceptions:
                trade_sundays.append(last_sunday_of_month)
        else:
            for i_month in range(1, 13):
                last_sunday_of_month = self.determine_nearest_sunday_before_date_exclusive(
                    dt.date(year if i_month < 12 else year + 1, i_month + 1 if i_month < 12 else 1, 1)
                )
                if last_sunday_of_month not in exceptions:
                    trade_sundays.append(last_sunday_of_month)
        return trade_sundays

    def _determine_trade_sunday_dates_after_2019(
            self, exceptions: List[dt.date], year: int, month: int
    ) -> List[dt.date]:
        trade_sundays = []
        if month:
            if month in (1, 4, 6, 8):
                last_sunday_of_month = self.determine_nearest_sunday_before_date_exclusive(
                    dt.date(year if month < 12 else year + 1, month + 1 if month < 12 else 1, 1)
                )
                if last_sunday_of_month not in exceptions:
                    trade_sundays.append(last_sunday_of_month)
        else:
            for i_month in (1, 4, 6, 8):
                last_sunday_of_month = self.determine_nearest_sunday_before_date_exclusive(
                    dt.date(year if i_month < 12 else year + 1, i_month + 1 if i_month < 12 else 1, 1)
                )
                if last_sunday_of_month not in exceptions:
                    trade_sundays.append(last_sunday_of_month)
        return trade_sundays

    def determine_trade_sunday_dates(self, year: int, month: int = None) -> List[dt.date]:
        if not isinstance(year, int):
            raise TypeError('year must be an int')
        if year < 1990:
            raise ValueError('year must be 1990 or later')
        if month is not None:
            if not isinstance(month, int):
                raise TypeError('month must be None or an int')
            if not 1 <= month <= 12:
                raise ValueError('month must be between 1 and 12 inclusive')
        possible_sunday_holiday_dates = self.determine_possible_sunday_holiday_dates(year)
        if year < 2018:
            trade_sundays = self._determine_trade_sunday_dates_before_2018(possible_sunday_holiday_dates, year, month)
        else:
            special_trade_sunday_dates = self.determine_special_trade_sunday_dates(year)
            exceptions = possible_sunday_holiday_dates + special_trade_sunday_dates
            if year == 2018:
                trade_sundays = self._determine_trade_sunday_dates_2018(
                    exceptions, possible_sunday_holiday_dates, year, month
                )
            elif year == 2019:
                trade_sundays = self._determine_trade_sunday_dates_2019(exceptions, year, month)
            else:
                trade_sundays = self._determine_trade_sunday_dates_after_2019(
                    exceptions, year, month
                )
            if month:
                for special_trade_sunday_date in special_trade_sunday_dates:
                    if special_trade_sunday_date.month == month:
                        trade_sundays.append(special_trade_sunday_date)
            else:
                trade_sundays.extend(special_trade_sunday_dates)
        trade_sundays.sort()
        return trade_sundays

    def determine_nearest_trade_sunday_after_date_inclusive(self, date: dt.date = None) -> dt.date:
        if date is not None and not isinstance(date, dt.date):
            raise TypeError('date must be None or a datetime.date')
        date = date or dt.date.today()
        year = date.year
        month = date.month
        while True:
            for trade_sunday in self.determine_trade_sunday_dates(year, month):
                if trade_sunday >= date:
                    return trade_sunday
            if month < 12:
                month += 1
            else:
                year += 1
                month = 1

    @commands.group(aliases=['niedzielehandlowe', 'handlowe', 'niedzielahandlowa', 'handlowa'], invoke_without_command=True, case_insensitive=True)
    @cooldown()
    async def trade_sundays(self, ctx):
        await self.bot.send(ctx, embeds=self.HELP.embeds)

    @trade_sundays.command(aliases=['najbliższa', 'najblizsza'])
    @cooldown()
    async def trade_sundays_nearest(self, ctx):
        nearest_sunday_date = self.determine_nearest_sunday_after_date_inclusive()
        nearest_trade_sunday_date = self.determine_nearest_trade_sunday_after_date_inclusive()
        if nearest_sunday_date == nearest_trade_sunday_date:
            emoji = '🛒'
            description = None
            if dt.date.today() == nearest_sunday_date:
                notice = 'Dzisiejsza niedziela jest handlowa'
            else:
                notice = f'Najbliższa niedziela, {nearest_sunday_date.strftime("%-d %B")}, będzie handlowa'
        else:
            emoji = '🚫'
            description = f'Następna niedziela handlowa to {nearest_trade_sunday_date.strftime("%-d %B")}.'
            if dt.date.today() == nearest_sunday_date:
                notice = 'Dzisiejsza niedziela nie jest handlowa'
            else:
                notice = f'Najbliższa niedziela, {nearest_sunday_date.strftime("%-d %B")}, nie będzie handlowa'
        embed = self.bot.generate_embed(emoji, notice, description)
        await self.bot.send(ctx, embed=embed)

    @trade_sundays.command(aliases=['terminarz', 'lista', 'spis'])
    @cooldown()
    async def trade_sundays_list(self, ctx, year: Optional[int], month: Optional[int]):
        month_names = [
            'Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik',
            'Listopad', 'Grudzień'
        ]
        year = year or dt.date.today().year
        if not 1990 <= year <= 9999:
            raise commands.BadArgument('Rok musi być w przedziale od 1990 do 9999 włącznie')
        if month is not None and not 1 <= month <= 12:
            raise commands.BadArgument('Miesiąc musi być w przedziale od 1 do 12 włącznie')
        trade_sunday_dates = self.determine_trade_sunday_dates(year, month)
        trade_sunday_dates_by_month = [[] for _ in range(12)]
        for trade_sunday_date in trade_sunday_dates:
            trade_sunday_dates_by_month[trade_sunday_date.month-1].append(str(trade_sunday_date.day))
        embed = self.bot.generate_embed('🗓', f'Niedziele handlowe w {year}')
        for i, i_month in enumerate(trade_sunday_dates_by_month):
            if i_month:
                embed.add_field(name=month_names[i], value=', '.join(i_month))
        await self.bot.send(ctx, embed=embed)

    @trade_sundays_list.error
    async def trade_sundays_list_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            embed = self.bot.generate_embed('⚠️', str(error))
            await self.bot.send(ctx, embed=embed)