Exemple #1
0
    async def background_task_league_end(self) -> None:
        tournament_channel_id = configuration.get_int(
            'tournament_reminders_channel_id')
        if not tournament_channel_id:
            logging.warning('tournament channel is not configured')
            return
        channel = self.get_channel(tournament_channel_id)
        if not isinstance(channel, discord.abc.Messageable):
            logging.warning('tournament channel could not be found')
            return

        while self.is_ready:
            try:
                league = await fetch_tools.fetch_json_async(
                    fetcher.decksite_url('/api/league'))
            except fetch_tools.FetchException as e:
                logging.error(
                    "Couldn't reach decksite or decode league json with error message(s) {e}",
                    e='; '.join(str(x) for x in e.args))
                logging.info('Sleeping for 5 minutes and trying again.')
                await asyncio.sleep(300)
                continue

            if not league:
                await asyncio.sleep(300)
                continue

            diff = round((dtutil.parse_rfc3339(league['end_date']) -
                          datetime.datetime.now(tz=datetime.timezone.utc)) /
                         datetime.timedelta(seconds=1))

            embed = discord.Embed(
                title=league['name'],
                description=
                'League ending soon - any active runs will be cut short.')
            if diff <= 60 * 60 * 24:
                embed.add_field(name='Ending in:',
                                value=dtutil.display_time(diff, 2))
                embed.set_image(url=fetcher.decksite_url('/favicon-152.png'))
                # See #2809.
                # pylint: disable=no-value-for-parameter,unexpected-keyword-arg
                await channel.send(embed=embed)
            if diff <= 5 * 60:
                # Five minutes, final warning.
                timer = 301
            elif diff <= 1 * 60 * 60:
                # 1 hour. Sleep until five minute warning.
                timer = diff - 300
            elif diff <= 24 * 60 * 60:
                # 1 day.  Sleep until one hour warning.
                timer = diff - 1800
            else:
                # Sleep for 1 day, plus enough to leave us with a whole number of days
                timer = 24 * 60 * 60 + diff % (24 * 60 * 60)

            if timer < 300:
                timer = 300
            await asyncio.sleep(timer)
        logging.warning('naturally stopping league reminders')
Exemple #2
0
async def respond_to_card_names(message: Message, client: Client) -> None:
    # Don't parse messages with Gatherer URLs because they use square brackets in the querystring.
    if 'gatherer.wizards.com' in message.content.lower():
        return
    queries = parse_queries(message.content)
    if len(queries) > 0:
        results = results_from_queries(queries)
        cards = []
        for i in results:
            (r, mode) = i
            if r.has_match() and not r.is_ambiguous():
                cards.extend(
                    cards_from_names_with_mode([r.get_best_match()], mode))
            elif r.is_ambiguous():
                cards.extend(
                    cards_from_names_with_mode(r.get_ambiguous_matches(),
                                               mode))
        await post_cards(client, cards, message.channel, message.author)

    matches = re.findall(
        r'https?://(?:www.)?tappedout.net/mtg-decks/(?P<slug>[\w-]+)/?',
        message.content,
        flags=re.IGNORECASE)
    for match in matches:
        data = {
            'url': 'https://tappedout.net/mtg-decks/{slug}'.format(slug=match)
        }
        fetcher.internal.post(fetcher.decksite_url('/add/'), data)
Exemple #3
0
async def background_task_tournaments():
    await BOT.client.wait_until_ready()
    tournament_channel_id = configuration.get('tournament_channel_id')
    if not tournament_channel_id:
        return
    channel = discord.Object(id=tournament_channel_id)
    while not BOT.client.is_closed:
        info = tournaments.next_tournament_info()
        diff = info['next_tournament_time_precise']
        if diff <= 14400:
            embed = discord.Embed(title=info['next_tournament_name'], description='Starting in {0}.'.format(info['next_tournament_time']), colour=0xDEADBF)
            embed.set_image(url=fetcher.decksite_url('/favicon-152.png'))
            await BOT.client.send_message(channel, embed=embed)

        if diff <= 300:
            # Five minutes, final warning.  Sleep until the tournament has started.
            timer = 301
        elif diff <= 1800:
            # Half an hour. Sleep until 5 minute warning.
            timer = diff - 300
        elif diff <= 3600:
            # One hour.  Sleep until half-hour warning.
            timer = diff - 1800
        else:
            # Wait until four hours before tournament.
            timer = min(3600, diff - 14400)
        await asyncio.sleep(timer)
def site_resources(args: str) -> Dict[str, str]:
    results = {}
    match = re.match('^s? ?([0-9]*|all) +', args)
    if match:
        season_prefix = 'seasons/' + match.group(1)
        args = args.replace(match.group(0), '', 1).strip()
    else:
        season_prefix = ''
    if ' ' in args:
        area, detail = args.split(' ', 1)
    else:
        area, detail = args, ''
    if area == 'archetype':
        area = 'archetypes'
    if area == 'card':
        area = 'cards'
    if area == 'person':
        area = 'people'
    sitemap = fetcher.sitemap()
    matches = [
        endpoint for endpoint in sitemap
        if endpoint.startswith('/{area}/'.format(area=area))
    ]
    if len(matches) > 0:
        detail = '{detail}/'.format(
            detail=fetcher.internal.escape(detail, True)) if detail else ''
        url = fetcher.decksite_url('{season_prefix}/{area}/{detail}'.format(
            season_prefix=season_prefix,
            area=fetcher.internal.escape(area),
            detail=detail))
        results[url] = args
    return results
Exemple #5
0
    async def background_task_tournaments(self) -> None:
        try:
            await self.wait_until_ready()
            tournament_channel_id = configuration.get_int(
                'tournament_channel_id')
            if not tournament_channel_id:
                return
            channel = self.get_channel(tournament_channel_id)
            while not self.is_closed:
                info = tournaments.next_tournament_info()
                diff = info['next_tournament_time_precise']
                if info['sponsor_name']:
                    message = 'A {sponsor} sponsored tournament'.format(
                        sponsor=info['sponsor_name'])
                else:
                    message = 'A free tournament'
                embed = discord.Embed(title=info['next_tournament_name'],
                                      description=message)
                if diff <= 0:
                    embed.add_field(
                        name='Starting now',
                        value=
                        'Check <#334220558159970304> for further annoucements')
                elif diff <= 14400:
                    embed.add_field(name='Starting in:',
                                    value=dtutil.display_time(diff, 2))
                    embed.add_field(name='Pre-register now:',
                                    value='https://gatherling.com')

                if diff <= 14400:
                    embed.set_image(
                        url=fetcher.decksite_url('/favicon-152.png'))
                    # See #2809.
                    # pylint: disable=no-value-for-parameter,unexpected-keyword-arg
                    await channel.send(embed=embed)

                if diff <= 300:
                    # Five minutes, final warning.  Sleep until the tournament has started.
                    timer = 301
                elif diff <= 1800:
                    # Half an hour. Sleep until 5 minute warning.
                    timer = diff - 300
                elif diff <= 3600:
                    # One hour.  Sleep until half-hour warning.
                    timer = diff - 1800
                else:
                    # Wait until four hours before tournament.
                    timer = 3600 + diff % 3600
                    if diff > 3600 * 6:
                        # The timer can afford to get off-balance by doing other background work.
                        await self.background_task_spoiler_season()
                        multiverse.update_bugged_cards()

                if timer < 300:
                    timer = 300
                print('diff={0}, timer={1}'.format(diff, timer))
                await asyncio.sleep(timer)
        except Exception:  # pylint: disable=broad-except
            await self.on_error('background_task_tournaments')
Exemple #6
0
    async def background_task_tournaments(self) -> None:
        tournament_channel_id = configuration.get_int(
            'tournament_reminders_channel_id')
        if not tournament_channel_id:
            logging.warning('tournament channel is not configured')
            return
        channel = self.get_channel(tournament_channel_id)
        if not isinstance(channel, discord.abc.Messageable):
            logging.warning('ERROR: could not find tournament_channel_id {id}',
                            id=tournament_channel_id)
            return
        while self.is_ready:
            info = tournaments.next_tournament_info()
            diff = info['next_tournament_time_precise']
            if info['sponsor_name']:
                message = 'A {sponsor} sponsored tournament'.format(
                    sponsor=info['sponsor_name'])
            else:
                message = 'A free tournament'
            embed = discord.Embed(title=info['next_tournament_name'],
                                  description=message)
            if diff <= 1:
                embed.add_field(
                    name='Starting now',
                    value='Check <#334220558159970304> for further annoucements'
                )
            elif diff <= 14400:
                embed.add_field(name='Starting in:',
                                value=dtutil.display_time(diff, 2))
                embed.add_field(name='Pre-register now:',
                                value='https://gatherling.com')

            if diff <= 14400:
                embed.set_image(url=fetcher.decksite_url('/favicon-152.png'))
                # See #2809.
                # pylint: disable=no-value-for-parameter,unexpected-keyword-arg
                await channel.send(embed=embed)

            if diff <= 300:
                # Five minutes, final warning.  Sleep until the tournament has started.
                timer = 301
            elif diff <= 1800:
                # Half an hour. Sleep until 5 minute warning.
                timer = diff - 300
            elif diff <= 3600:
                # One hour.  Sleep until half-hour warning.
                timer = diff - 1800
            else:
                # Sleep for one hour plus enough to have a whole number of hours left.
                timer = 3600 + diff % 3600
                if diff > 3600 * 6:
                    # The timer can afford to get off-balance by doing other background work.
                    await multiverse.update_bugged_cards_async()

            if timer < 300:
                timer = 300
            await asyncio.sleep(timer)
        logging.warning('naturally stopping tournament reminders')
 async def tournament(self, bot, channel):
     """!tournament` Get information about the next tournament."""
     t = tournaments.next_tournament_info()
     await bot.client.send_message(
         channel,
         'The next tournament is {name} in {time}.\nSign up on <http://gatherling.com/>.\nMore information: {url}'
         .format(name=t['next_tournament_name'],
                 time=t['next_tournament_time'],
                 url=fetcher.decksite_url('/tournaments/')))
Exemple #8
0
 async def on_member_join(self, member: Member) -> None:
     print('{0} joined {1} ({2})'.format(member.mention, member.server.name,
                                         member.server.id))
     is_pd_server = member.server.id == '207281932214599682'
     # is_test_server = member.server.id == '226920619302715392'
     if is_pd_server:  # or is_test_server:
         greeting = "Hey there {mention}, welcome to the Penny Dreadful community!  Be sure to set your nickname to your MTGO username, and check out <{url}> if you haven't already.".format(
             mention=member.mention, url=fetcher.decksite_url('/'))
         await self.client.send_message(member.server.default_channel,
                                        greeting)
Exemple #9
0
 async def on_member_join(self, member: Member) -> None:
     if member.bot:
         return
     # is_test_server = member.guild.id == 226920619302715392
     if is_pd_server(member.guild):  # or is_test_server:
         greeting = "Hey there {mention}, welcome to the Penny Dreadful community!  Be sure to set your nickname to your MTGO username, and check out <{url}> if you haven't already.".format(
             mention=member.mention, url=fetcher.decksite_url('/'))
         chan = member.guild.get_channel(
             207281932214599682
         )  #general (Yes, the guild ID is the same as the ID of it's first channel.  It's not a typo)
         await chan.send(greeting)
Exemple #10
0
 async def on_member_join(self, member: Member) -> None:
     print('{0} joined {1} ({2})'.format(member.mention, member.guild.name,
                                         member.guild.id))
     is_pd_server = member.guild.id == 207281932214599682
     # is_test_server = member.guild.id == 226920619302715392
     if is_pd_server:  # or is_test_server:
         greeting = "Hey there {mention}, welcome to the Penny Dreadful community!  Be sure to set your nickname to your MTGO username, and check out <{url}> if you haven't already.".format(
             mention=member.mention, url=fetcher.decksite_url('/'))
         chan = member.guild.text_channels[0]
         print(f'Greeting in {chan}')
         await chan.send(greeting)
Exemple #11
0
async def respond_to_card_names(message, bot):
    # Don't parse messages with Gatherer URLs because they use square brackets in the querystring.
    if 'gatherer.wizards.com' in message.content.lower():
        return
    queries = parse_queries(message.content)
    if len(queries) > 0:
        cards = cards_from_queries(queries)
        await bot.post_cards(cards, message.channel, message.author)

    matches = re.findall(r'https?://(?:www.)?tappedout.net/mtg-decks/(?P<slug>[\w-]+)/?', message.content, flags=re.IGNORECASE)
    for match in matches:
        data = {"url": "http://tappedout.net/mtg-decks/{slug}".format(slug=match)}
        fetcher.internal.post(fetcher.decksite_url('/add/'), data)
Exemple #12
0
 async def resources(self, bot, channel, args):
     """`!resources {args}` Link to useful pages related to `args`. Examples: 'tournaments', 'card Hymn to Tourach', 'deck check', 'league'."""
     results = {}
     if len(args) > 0:
         results.update(resources_resources(args))
         results.update(site_resources(args))
     s = ''
     if len(results) == 0:
         s = 'PD resources: <{url}>'.format(
             url=fetcher.decksite_url('/resources/'))
     else:
         for url, text in results.items():
             s += '{text}: <{url}>\n'.format(text=text, url=url)
     await bot.client.send_message(channel, s)
 async def resources(self, client: Client, channel: Channel, args: str, author: Member, **_: Dict[str, Any]) -> None:
     """`!resources {args}` Link to useful pages related to `args`. Examples: 'tournaments', 'card Hymn to Tourach', 'deck check', 'league'."""
     results = {}
     if len(args) > 0:
         results.update(resources_resources(args))
         results.update(site_resources(args))
     s = ''
     if len(results) == 0:
         s = 'PD resources: <{url}>'.format(url=fetcher.decksite_url('/resources/'))
     elif len(results) > 10:
         s = '{author}: Too many results, please be more specific.'.format(author=author.mention)
     else:
         for url, text in results.items():
             s += '{text}: <{url}>\n'.format(text=text, url=url)
     await client.send_message(channel, s)
Exemple #14
0
def site_resources(args):
    results = {}
    if ' ' in args.strip():
        area, detail = args.strip().split(' ', 1)
    else:
        area, detail = args.strip(), ''
    if area == 'card':
        area = 'cards'
    if area == 'person':
        area = 'people'
    sitemap = fetcher.sitemap()
    matches = [endpoint for endpoint in sitemap if endpoint.startswith('/{area}/'.format(area=area))]
    if len(matches) > 0:
        url = fetcher.decksite_url('/{area}/{detail}/'.format(area=fetcher.internal.escape(area), detail=fetcher.internal.escape(detail)))
        results[url] = args
    return results
Exemple #15
0
def card_history(c: Card) -> str:
    data: Dict[int, bool] = {}
    for format_name, status in c.legalities.items():
        if 'Penny Dreadful ' in format_name and status == 'Legal':
            season_id = seasons.SEASONS.index(
                format_name.replace('Penny Dreadful ', '')) + 1
            data[season_id] = True
    data[seasons.current_season_num()] = c.legalities.get('Penny Dreadful', None) == 'Legal'
    s = '   '
    for i in range(1, seasons.current_season_num() + 1):
        s += f'{i} '
        s += ':white_check_mark:' if data.get(i, False) else ':no_entry_sign:'
        s += '   '
    s = s.strip()
    s += '\n<' + fetcher.decksite_url('/seasons/all/cards/{name}/'.format(
        name=fetch_tools.escape(c.name, skip_double_slash=True))) + '>'
    return s
async def randomdeck(ctx: MtgContext) -> None:
    """A random deck from the current season."""
    blob = fetch_tools.fetch_json(fetcher.decksite_url('/api/randomlegaldeck'))
    if 'error' in blob or 'url' not in blob:
        await ctx.send(blob.get('msg', ''))
    else:
        ctn = blob.get('competition_type_name', None)
        if ctn is not None:
            if ctn == 'Gatherling' and blob['finish'] == 1:
                record = 'won'
            elif ctn == 'Gatherling' and blob['finish'] <= blob['competition_top_n']:
                record = f"made Top {blob['competition_top_n']} of"
            else:
                draws = f"-{blob['draws']}" if blob['draws'] > 0 else ''
                record = f"went {blob['wins']}-{blob['losses']}{draws} in"
            preamble = f"{blob['person']} {record} {blob['competition_name']} with this:\n"
        else:
            preamble = f"{blob['person']} posted this on {blob['source_name']}:\n"
        await ctx.send(preamble + blob['url'])
async def resources(ctx: MtgContext, *, args: Optional[str]) -> None:
    """Useful pages related to `args`. Examples: 'tournaments', 'card Naturalize', 'deckcheck', 'league'."""
    results = {}
    if args is None:
        args = ''
    if len(args) > 0:
        results.update(resources_resources(args))
        results.update(site_resources(args))
    s = ''
    if len(results) == 0:
        s = 'PD resources: <{url}>'.format(
            url=fetcher.decksite_url('/resources/'))
    elif len(results) > 10:
        s = '{author}: Too many results, please be more specific.'.format(
            author=ctx.author.mention)
    else:
        for url, text in results.items():
            s += '{text}: <{url}>\n'.format(text=text, url=url)
    await ctx.send(s)
Exemple #18
0
async def background_task_tournaments() -> None:
    await BOT.client.wait_until_ready()
    tournament_channel_id = configuration.get('tournament_channel_id')
    if not tournament_channel_id:
        return
    channel = discord.Object(id=tournament_channel_id)
    while not BOT.client.is_closed:
        info = tournaments.next_tournament_info()
        diff = info['next_tournament_time_precise']
        if diff <= 0:
            message = 'Tournament starting!'
        elif diff <= 14400:
            message = 'Starting: {0}.'.format(dtutil.display_time(diff, 2))

        if diff <= 14400:
            embed = discord.Embed(title=info['next_tournament_name'],
                                  description=message)
            embed.set_image(url=fetcher.decksite_url('/favicon-152.png'))
            # See #2809.
            # pylint: disable=no-value-for-parameter,unexpected-keyword-arg
            await BOT.client.send_message(channel, embed=embed)

        if diff <= 300:
            # Five minutes, final warning.  Sleep until the tournament has started.
            timer = 301
        elif diff <= 1800:
            # Half an hour. Sleep until 5 minute warning.
            timer = diff - 300
        elif diff <= 3600:
            # One hour.  Sleep until half-hour warning.
            timer = diff - 1800
        else:
            # Wait until four hours before tournament.
            timer = 3600 + diff % 3600
            if diff > 3600 * 6:
                # The timer can afford to get off-balance by doing other background work.
                await background_task_spoiler_season()
                multiverse.update_bugged_cards()

        if timer < 300:
            timer = 300
        print('diff={0}, timer={1}'.format(diff, timer))
        await asyncio.sleep(timer)
Exemple #19
0
 async def resources(self, bot, channel, args):
     """`!resources {args}` Link to useful pages related to `args`.
        Specifically – look for a section of pennydreadfulmagic.com that fits the description in {args}
        and links that match in /resources/.
        Examples:
            `!resources tournaments`
            `!resources card Hymn to Tourach`
            `!resources deck check`
            `!resources league`
            `!resources`
         """
     results = {}
     if len(args) > 0:
         results.update(resources_resources(args))
         results.update(site_resources(args))
     s = ''
     if len(results) == 0:
         s = 'PD resources: <{url}>'.format(url=fetcher.decksite_url('/resources/'))
     else:
         for url, text in results.items():
             s += '{text}: <{url}>\n'.format(text=text, url=url)
     await bot.client.send_message(channel, s)
Exemple #20
0
 async def tournament(self, channel: TextChannel, **_: Dict[str,
                                                            Any]) -> None:
     """`!tournament` Get information about the next tournament."""
     t = tournaments.next_tournament_info()
     prev = tournaments.previous_tournament_info()
     if prev['near']:
         started = 'it started '
     else:
         started = ''
     prev_message = 'The last tournament was {name}, {started}{time} ago'.format(
         name=prev['next_tournament_name'],
         started=started,
         time=prev['next_tournament_time'])
     next_time = 'in ' + t['next_tournament_time'] if t[
         'next_tournament_time'] != dtutil.display_time(
             0, 0) else t['next_tournament_time']
     await channel.send(
         'The next tournament is {name} {next_time}.\nSign up on <http://gatherling.com/>\nMore information: {url}\n{prev_message}'
         .format(name=t['next_tournament_name'],
                 next_time=next_time,
                 prev_message=prev_message,
                 url=fetcher.decksite_url('/tournaments/')))
Exemple #21
0
 async def tournament(self, bot, channel):
     """`!tournament` Get information about the next tournament."""
     t = tournaments.next_tournament_info()
     prev = tournaments.previous_tournament_info()
     if prev['near']:
         started = "it started "
     else:
         started = ""
     prev_message = "The last tournament was {name}, {started}{time} ago".format(
         name=prev['next_tournament_name'],
         started=started,
         time=prev['next_tournament_time'])
     next_time = 'in ' + t['next_tournament_time'] if t[
         'next_tournament_time'] != dtutil.display_time(
             0, 0) else t['next_tournament_time']
     await bot.client.send_message(
         channel,
         'The next tournament is {name} {next_time}.\nSign up on <http://gatherling.com/>\nMore information: {url}\n{prev_message}'
         .format(name=t['next_tournament_name'],
                 next_time=next_time,
                 prev_message=prev_message,
                 url=fetcher.decksite_url('/tournaments/')))
Exemple #22
0
async def explain(ctx: MtgContext, *, thing: Optional[str]) -> None:
    """Answers for Frequently Asked Questions
`!explain`. Get a list of things the bot knows how to explain.
`!explain {thing}`. Print commonly needed explanation for 'thing'."""
    num_tournaments = inflect.engine().number_to_words(
        len(tournaments.all_series_info()))
    explanations: Dict[str, Tuple[str, Dict[str, str]]] = {
        'archetype': ("""
            Archetypes are manually reviewed by a human on an irregular basis.
            Prior to that a deck will have either its assigned archetype on Gatherling (tournament decks), nothing, or a best-guess based on the most similar reviewed deck (league decks).
            If you want to help out let us know.
            """, {}),
        'bugs':
        ('We keep track of cards that are bugged on Magic Online. We allow the playing of cards with known bugs in Penny Dreadful under certain conditions. See the full rules on the website.',
         {
             'Known Bugs List':
             fetcher.decksite_url('/bugs/'),
             'Tournament Rules':
             fetcher.decksite_url('/tournaments/#bugs'),
             'Bugged Cards Database':
             'https://github.com/PennyDreadfulMTG/modo-bugs/issues/'
         }),
        'deckbuilding': ("""
            The best way to build decks is to use a search engine that supports Penny Dreadful legality (`f:pd`) like Scryfall.
            You can find Penny Dreadful decklists from tournaments, leagues and elsewhere at pennydreadfulmagic.com.
            """, {
            'Scryfall':
            'https://scryfall.com/',
            'Latest Decks':
            fetcher.decksite_url('/'),
            'Legal Cards List':
            'http://pdmtgo.com/legal_cards.txt'
        }),
        'decklists': ("""
            You can find Penny Dreadful decklists from tournaments, leagues and elsewhere at pennydreadfulmagic.com
            """, {
            'Latest Decks': fetcher.decksite_url('/')
        }),
        'doorprize':
        ("The door prize is 1 tik credit with Cardhoarder, awarded to one randomly-selected player that completes the Swiss rounds but doesn't make top 8.",
         {}),
        'language': ("""
            To change the language you see the site in use the language switcher in the top-left hand corner (desktop only) or follow the link below for English.
            """, {
            'PDM in English': fetcher.decksite_url('/?locale=en')
        }),
        'league': ("""
            Leagues last for roughly a month. You may enter any number of times but only one deck at a time.
            You play five matches per run. You can join the league at any time.
            To find a game sign up and then create a game in Constructed, Specialty, Freeform Tournament Practice with "Penny Dreadful League" as the comment.
            Top 8 finishers on each month's league leaderboard win credit with MTGO Traders.
            When you complete a five match league run for the first time ever you will get 1 tik credit with MTGO Traders (at the end of the month).
            """, {
            'More Info': fetcher.decksite_url('/league/'),
            'Sign Up': fetcher.decksite_url('/signup/'),
            'Current League': fetcher.decksite_url('/league/current/')
        }),
        'netdecking': ("""
            Netdecking is not only allowed, it is encouraged! Most deck creators are happy when others play their decks!
            You can find the best tournament winning decks in the link below. Sort by records to find the best tournament winning decks!
            """, {
            'Decklists': fetcher.decksite_url('/decks/'),
        }),
        'noshow': ("""
            If your opponent does not join your game please @-message them on Discord and contact them on Magic Online.
            If you haven't heard from them by 10 minutes after the start of the round let the Tournament Organizer know.
            You will receive a 2-0 win and your opponent will be dropped from the competition.
            """, {}),
        'onegame': ("""
            If your opponent concedes or times out before the match completes, PDBot will not report automatically.
            If you feel enough of a match was played you may manually report 2-x where x is the number of games your opponent won.
            """, {
            'Report': fetcher.decksite_url('/report/')
        }),
        'playing': ("""
            To get a match go to Constructed, Specialty, Freeform Tournament Practice on MTGO and create a match with "Penny Dreadful" in the comments.
            """, {}),
        'prices': (f"""
            The price output contains current price.
            If the price is low enough it will show season-low and season-high also.
            If the card has been {card_price.MAX_PRICE_TEXT} or less at any point this season it will also include the amount of time (as a percentage) the card has spent at {card_price.MAX_PRICE_TEXT} or below this week, month and season.
            """, {}),
        'prizes': ("""
            Gatherling tournaments pay prizes to the Top 8 in Cardhoarder credit.
            This credit will appear when you trade with one of their bots on Magic Online.
            One player not making Top 8 but playing all the Swiss rounds will be randomly allocated the door prize.
            Prizes are credited once a week usually on the Friday or Saturday following the tournament but may sometimes take longer.
            """, {
            'More Info': fetcher.decksite_url('/tournaments/')
        }),
        'replay': ("""
            You can play the same person a second time on your league run as long as they have started a new run. The same two runs cannot play each other twice.
            """, {}),
        'reporting': ("""
            """, {}),
        'retire':
        ('To retire from a league run message PDBot on MTGO with `!retire`. If you have authenticated with Discord on pennydreadfulmagic.com you can say `!retire` on Discord or retire on the website.',
         {
             'Retire': fetcher.decksite_url('/retire/')
         }),
        'rotation': (f"""
            Legality is set a week after the release of a Standard-legal set on Magic Online.
            Prices are checked every hour for a week from the set release. Anything {card_price.MAX_PRICE_TEXT} or less for half or more of all checks is legal for the season.
            Any version of a card on the legal cards list is legal.
            """, {}),
        'spectating': ("""
            Spectating tournament and league matches is allowed and encouraged.
            Please do not write anything in chat except to call PDBot's `!record` command to find out the current score in games.
            """, {}),
        'tournament': ("""
            We have {num_tournaments} free-to-enter weekly tournaments that award trade credit prizes from Cardhoarder.
            They are hosted on gatherling.com along with a lot of other player-run Magic Online events.
            """.format(num_tournaments=num_tournaments), {
            'More Info': fetcher.decksite_url('/tournaments/'),
            'Sign Up': 'https://gatherling.com/',
        }),
        'username': ("""
            Please change your Discord username to include your MTGO username so we can know who you are.
            To change, right-click your username.
            This will not affect any other Discord channel.
            """, {}),
        'verification': ("""
            Gatherling verification is currently broken.
            It no longer does anything except put a green tick by your name anyway.
            """, {}),
    }
    reporting_explanations: Dict[str, Tuple[str, Dict[str, str]]] = {
        'tournament': ("""
            For tournaments PDBot is information-only, *both* players must report near the top of Player CP (or follow the link at the top of any Gatherling page).
            """, {
            'Gatherling': 'https://gatherling.com/player.php',
        }),
        'league': ("""
            If PDBot reports your league match in #league in Discord you don't need to do anything. If not, either player can report.
            """, {
            'League Report': fetcher.decksite_url('/report/')
        })
    }
    keys = sorted(explanations.keys())
    explanations['drop'] = explanations['retire']
    explanations['legality'] = explanations['rotation']
    explanations['spectate'] = explanations['spectating']
    explanations['tournaments'] = explanations['tournament']
    explanations['watching'] = explanations['spectating']
    explanations['spectate'] = explanations['spectating']
    explanations['verify'] = explanations['verification']
    # strip trailing 's' to make 'leagues' match 'league' and simliar without affecting the output of `!explain` to be unnecessarily plural.
    if thing is None:
        thing = ''
    word = thing.lower().replace(' ', '').rstrip('s')
    if len(word) > 0:
        for k in explanations:
            if k.startswith(word):
                word = k
    try:
        if word == 'reporting':
            if is_tournament_channel(ctx.channel):
                explanation = reporting_explanations['tournament']
            else:
                explanation = reporting_explanations['league']
        else:
            explanation = explanations[word]

        s = '{text}\n'.format(text=textwrap.dedent(explanation[0]))
    except KeyError:
        usage = 'I can explain any of these things: {things}'.format(
            things=', '.join(sorted(keys)))
        await ctx.send(usage)
        return
    for k in sorted(explanation[1].keys()):
        s += '{k}: <{v}>\n'.format(k=k, v=explanation[1][k])
    await ctx.send(s)
Exemple #23
0
async def pd500(ctx: MtgContext) -> None:
    """Display a link to the PD 500 information page."""
    await ctx.send(fetcher.decksite_url('/tournaments/pd500/'))
 async def explain(self, bot, channel, args):
     """
         `!explain`. Get a list of things the bot knows how to explain.
         `!explain {thing}`. Print commonly needed explanation for 'thing'.
     """
     explanations = {
         'decklists': [
             """
             You can find Penny Dreadful decklists from tournaments, leagues and elsewhere at pennydreadfulmagic.com
             """, {
                 'Latest Decks': fetcher.decksite_url('/')
             }
         ],
         'league': [
             """
             Leagues last for roughly a month. You may enter any number of times but only one deck at a time.
             You play 5 matches per run. You can join the league at any time.
             The league pays prizes in tix for top players and (some) 5-0 runs.
             To find a game sign up and then create a game in Just for Fun with "Penny Dreadful League" as the comment.
             """, {
                 'More Info': fetcher.decksite_url('/league/'),
                 'Sign Up': fetcher.decksite_url('/signup/'),
                 'Current League': fetcher.decksite_url('/league/current/')
             }
         ],
         'legality': [
             """
             Legality is determined at the release of a Standard-legal set on MTGO.
             Prices are checked every hour for a week. Anything 1c or less for half or more of all checks is legal for the season.
             Cards from the just-released set are added (nothing removed) a couple of weeks later via a supplemental rotation after prices have settled a little.
             """, {
                 'Deck Checker': 'http://pdmtgo.com/deck_check.html',
                 'Legal Cards List': 'http://pdmtgo.com/legal_cards.txt'
             }
         ],
         'playing': [
             """
             To get a match go to Constructed Open Play, Just for Fun on MTGO and create a Freeform game with "Penny Dreadful" in the comments.
             """, {}
         ],
         'price': [
             """
             The price output contains current price.
             If the price is low enough it will show season-low and season-high also.
             If the card has been 1c at any point this season it will also include the amount of time (as a percentage) the card has spent at 1c or below this week, month and season.
             """, {}
         ],
         'report': [
             """
             For gatherling.com tournaments PDBot is information-only, *both* players must report at the bottom of Player CP.
             If PDBot reports your match in Discord you don't need to do anything. If not, either player can report.
             """, {
                 'Gatherling': 'http://gatherling.com/player.php',
                 'League Report': fetcher.decksite_url('/report/')
             }
         ],
         'retire': [
             'To retire from a league run message PDBot on MTGO with !retire.',
             {}
         ],
         'tournament': [
             """
             We have three free-to-enter weekly tournaments with prizes from cardhoarder.com.
             They are hosted on gatherling.com along with a lot of other player-run MTGO events.
             """, {
                 'More Info': fetcher.decksite_url('/tournaments/'),
                 'Sign Up': 'http://gatherling.com/',
             }
         ]
     }
     keys = sorted(explanations.keys())
     explanations['drop'] = explanations['retire']
     explanations['rotation'] = explanations['legality']
     explanations['tournaments'] = explanations['tournament']
     word = args.strip()
     try:
         s = '{text}\n'.format(text=textwrap.dedent(explanations[word][0]))
     except KeyError:
         usage = 'I can explain any of these things: {things}'.format(
             things=', '.join(sorted(keys)))
         return await bot.client.send_message(channel, usage)
     for k in sorted(explanations[word][1].keys()):
         s += '{k}: {v}\n'.format(k=k, v=explanations[word][1][k])
     await bot.client.send_message(channel, s)
Exemple #25
0
    async def explain(self, client: Client, channel: Channel, args: str,
                      **_: Dict[str, Any]) -> None:
        """`!explain`. Get a list of things the bot knows how to explain.
`!explain {thing}`. Print commonly needed explanation for 'thing'."""
        num_tournaments = inflect.engine().number_to_words(
            len(tournaments.all_series_info()))
        explanations: Dict[str, Tuple[str, Dict[str, str]]] = {
            'bugs':
            ('We keep track of cards that are bugged on Magic Online. We allow the playing of cards with known bugs in Penny Dreadful under certain conditions. See the full rules on the website.',
             {
                 'Known Bugs List':
                 fetcher.decksite_url('/bugs/'),
                 'Tournament Rules':
                 fetcher.decksite_url('/tournaments/#bugs'),
                 'Bugged Cards Database':
                 'https://github.com/PennyDreadfulMTG/modo-bugs/issues/'
             }),
            'deckbuilding': ("""
                The best way to build decks is to use a search engine that supports Penny Dreadful legality (`f:pd`) like Scryfall.
                You can find Penny Dreadful decklists from tournaments, leagues and elsewhere at pennydreadfulmagic.com.
                """, {
                'Scryfall':
                'https://scryfall.com/',
                'Latest Decks':
                fetcher.decksite_url('/'),
                'Legal Cards List':
                'http://pdmtgo.com/legal_cards.txt'
            }),
            'decklists': ("""
                You can find Penny Dreadful decklists from tournaments, leagues and elsewhere at pennydreadfulmagic.com
                """, {
                'Latest Decks': fetcher.decksite_url('/')
            }),
            'doorprize':
            ("The door prize is 1 tik credit with Cardhoarder, awarded to one randomly-selected player that completes the Swiss rounds but doesn't make top 8.",
             {}),
            'league': ("""
                Leagues last for roughly a month. You may enter any number of times but only one deck at a time.
                You play five matches per run. You can join the league at any time.
                To find a game sign up and then create a game in Just for Fun with "Penny Dreadful League" as the comment.
                Top 8 finishers on each month's league leaderboard win credit with MTGO Traders.
                When you complete a five match league run for the first time ever you will get 1 tik credit with MTGO Traders.
                """, {
                'More Info':
                fetcher.decksite_url('/league/'),
                'Sign Up':
                fetcher.decksite_url('/signup/'),
                'Current League':
                fetcher.decksite_url('/league/current/')
            }),
            'legality': ("""
                Legality is determined at the release of a Standard-legal set on Magic Online.
                Prices are checked every hour for a week. Anything 1c or less for half or more of all checks is legal for the season.
                Cards from the just-released set are added (nothing removed) a couple of weeks later via a supplemental rotation after prices have settled a little.
                Any version of a card on the legal cards list is legal.
                """, {
                'Deck Checker':
                'http://pdmtgo.com/deck_check.html',
                'Legal Cards List':
                'http://pdmtgo.com/legal_cards.txt',
                'Rotation Speculation':
                fetcher.decksite_url('/rotation/speculation/'),
                'Rotation Changes':
                fetcher.decksite_url('/rotation/changes/')
            }),
            'noshow': ("""
                If your opponent does not join your game please @-message them on Discord and contact them on Magic Online.
                If you haven't heard from them by 10 minutes after the start of the round let the Tournament Organizer know.
                You will receive a 2-0 win and your opponent will be dropped from the competition.
                """, {}),
            'playing': ("""
                To get a match go to Constructed Open Play, Just for Fun on MTGO and create a Freeform game with "Penny Dreadful" in the comments.
                """, {}),
            'prices': ("""
                The price output contains current price.
                If the price is low enough it will show season-low and season-high also.
                If the card has been 1c at any point this season it will also include the amount of time (as a percentage) the card has spent at 1c or below this week, month and season.
                """, {}),
            'prizes': ("""
                Gatherling tournaments pay prizes to the Top 8 in Cardhoarder credit.
                This credit will appear when you trade with one of their bots on Magic Online.
                One player not making Top 8 but playing all the Swiss rounds will be randomly allocated the door prize.
                Prizes are credited once a week usually on the Friday or Saturday following the tournament but may sometimes take longer.
                """, {
                'More Info': fetcher.decksite_url('/tournaments/')
            }),
            'report': ("""
                For gatherling.com tournaments PDBot is information-only, *both* players must report near the top of Player CP.
                If PDBot reports your league match in Discord you don't need to do anything (only league matches, tournament matches must still be reported). If not, either player can report.
                """, {
                'Gatherling': 'https://gatherling.com/player.php',
                'League Report': fetcher.decksite_url('/report/')
            }),
            'retire':
            ('To retire from a league run message PDBot on MTGO with `!retire`. If you have authenticated with Discord on pennydreadfulmagic.com you can say `!retire` on Discord or retire on the website.',
             {
                 'Retire': fetcher.decksite_url('/retire/')
             }),
            'tournament': ("""
                We have {num_tournaments} free-to-enter weekly tournaments, most of which have prizes from Cardhoarder.
                They are hosted on gatherling.com along with a lot of other player-run Magic Online events.
                """.format(num_tournaments=num_tournaments), {
                'More Info': fetcher.decksite_url('/tournaments/'),
                'Sign Up': 'https://gatherling.com/',
            }),
            'username': ("""
                Please change your Discord username to include your MTGO username so we can know who you are.
                To change, right-click your username.
                This will not affect any other Discord channel.
                """, {})
        }
        keys = sorted(explanations.keys())
        explanations['drop'] = explanations['retire']
        explanations['rotation'] = explanations['legality']
        explanations['tournaments'] = explanations['tournament']
        word = args.strip().lower().rstrip(
            's'
        )  # strip trailing 's' to make 'leagues' match 'league' and simliar without affecting the output of `!explain` to be unnecessarily plural.
        if len(word) > 0:
            for k in explanations:
                if k.startswith(word):
                    word = k
        try:
            s = '{text}\n'.format(text=textwrap.dedent(explanations[word][0]))
        except KeyError:
            usage = 'I can explain any of these things: {things}'.format(
                things=', '.join(sorted(keys)))
            return await client.send_message(channel, usage)
        for k in sorted(explanations[word][1].keys()):
            s += '{k}: <{v}>\n'.format(k=k, v=explanations[word][1][k])
        await client.send_message(channel, s)
Exemple #26
0
async def kickoff(ctx: MtgContext) -> None:
    """Display a link to the Season Kick Off information page."""
    await ctx.send(fetcher.decksite_url('/tournaments/kickoff/'))