def message() -> str: upcoming = next_rotation_ex() diff = next_rotation() - dtutil.now() s = dtutil.display_time(int(diff.total_seconds())) if upcoming.code == '???': s = dtutil.display_time(int(diff.total_seconds()), 1) return f'The next rotation is roughly {s} away' return f'The next rotation is in {s}'
def test_display_time() -> None: assert dtutil.display_time(60 * 60 * 2 - 1) == '2 hours' assert dtutil.display_time(60 * 60 * 2 + 1) == '2 hours' assert dtutil.display_time((24 * 60 * 60 * 3) + (60 * 60 * 2) + (60 * 59)) == '3 days, 3 hours' assert dtutil.display_time((24 * 60 * 60 * 3) + (60 * 60 * 2) + (60 * 29)) == '3 days, 2 hours' assert dtutil.display_time(2417366.810318) == '4 weeks'
def test_rounding() -> None: assert dtutil.display_time(121, granularity=1) == '2 minutes' assert dtutil.display_time(121, granularity=2) == '2 minutes, 1 second' assert dtutil.display_time(121, granularity=3) == '2 minutes, 1 second' assert dtutil.display_time(159, granularity=1) == '3 minutes' assert dtutil.display_time(91, granularity=1) == '2 minutes' assert dtutil.display_time(6900, granularity=2) == '1 hour, 55 minutes' assert dtutil.display_time(345610, granularity=4) == '4 days, 10 seconds' assert dtutil.display_time(4860) == '1 hour, 21 minutes' assert dtutil.display_time(86400) == '1 day' assert dtutil.display_time(0, 2) == 'now'
def text(): full = next_rotation() supplemental = next_supplemental() now = dtutil.now() sdiff = supplemental - now diff = full - now if sdiff < diff: return "The supplemental rotation is in {sdiff} (The next full rotation is in {diff})".format( diff=dtutil.display_time(diff.total_seconds()), sdiff=dtutil.display_time(sdiff.total_seconds())) return "The next rotation is in {diff}".format( diff=dtutil.display_time(diff.total_seconds()))
def message() -> str: full = next_rotation() supplemental = next_supplemental() now = dtutil.now() sdiff = supplemental - now diff = full - now if sdiff < diff: return 'The supplemental rotation is in {sdiff} (The next full rotation is in {diff})'.format( diff=dtutil.display_time(diff.total_seconds()), sdiff=dtutil.display_time(sdiff.total_seconds())) return 'The next rotation is in {diff}'.format( diff=dtutil.display_time(diff.total_seconds()))
def test_display_time() -> None: assert dtutil.display_time(60 * 60 * 2 - 1 * 60) == '1 hour, 59 minutes' assert dtutil.display_time(60 * 60 * 2 + 1 * 60) == '2 hours, 1 minute' assert dtutil.display_time(60 * 60 * 2 - 1) == '2 hours' assert dtutil.display_time(60 * 60 * 2 + 1) == '2 hours' assert dtutil.display_time(60 * 60 * 2 - 1, 1) == '2 hours' assert dtutil.display_time(60 * 60 * 2 + 1, 1) == '2 hours' assert dtutil.display_time((24 * 60 * 60 * 3) + (60 * 60 * 2) + (60 * 59)) == '3 days, 3 hours' assert dtutil.display_time((24 * 60 * 60 * 3) + (60 * 60 * 2) + (60 * 29)) == '3 days, 2 hours' assert dtutil.display_time(2417367) == '3 weeks, 6 days'
def next_tournament_info(): now = dtutil.now(dtutil.GATHERLING_TZ) now_ts = dtutil.dt2ts(dtutil.now()) pdm_time = rrule.rrule(rrule.WEEKLY, byhour=19, byminute=0, bysecond=0, dtstart=now, byweekday=rrule.MO)[0] pdt_time = rrule.rrule(rrule.WEEKLY, byhour=19, byminute=0, bysecond=0, dtstart=now, byweekday=rrule.TH)[0] pds_time = rrule.rrule(rrule.WEEKLY, byhour=13, byminute=30, bysecond=0, dtstart=now, byweekday=rrule.SU)[0] if pdm_time < pdt_time and pdm_time < pds_time: next_tournament_name = 'Penny Dreadful Monday' next_time = pdm_time elif pdt_time < pds_time: next_tournament_name = 'Penny Dreadful Thursday' next_time = pdt_time else: next_tournament_name = 'Penny Dreadful Sunday' next_time = pds_time next_tournament_time_precise = dtutil.dt2ts(next_time) - now_ts next_tournament_time = dtutil.display_time(next_tournament_time_precise, granularity=1) return { 'next_tournament_name': next_tournament_name, 'next_tournament_time': next_tournament_time, 'next_tournament_time_precise': next_tournament_time_precise, 'pdm_time': pdm_time, 'pds_time': pds_time, 'pdt_time': pdt_time }
def last_switcheroo(self) -> str: last_switcheroo = stats.calc_last_switcheroo() if last_switcheroo: diff = dtutil.dt2ts(dtutil.now()) - dtutil.dt2ts( last_switcheroo.start_time_aware()) return dtutil.display_time(diff) return 'unknown'
def price_info(c: Card) -> str: try: p = fetcher.card_price(c.name) except FetchException: return 'Price unavailable' if p is None: return 'Not available online' # Currently disabled s = '{price}'.format(price=format_price(p['price'])) try: if float(p['low']) <= 0.05: s += ' (low {low}, high {high}'.format( low=format_price(p['low']), high=format_price(p['high'])) if float(p['low']) <= MAX_PRICE_TIX and not short: s += ', {week}% this week, {month}% this month, {season}% this season'.format( week=round(float(p['week']) * 100.0), month=round(float(p['month']) * 100.0), season=round(float(p['season']) * 100.0)) s += ')' age = dtutil.dt2ts(dtutil.now()) - p['time'] if age > 60 * 60 * 2: s += '\nWARNING: price information is {display} old'.format( display=dtutil.display_time(age, 1)) except TypeError as e: print(f'Unable to get price info string from {p} because of {e}') return 'Price information is incomplete' return s
def single_card_text_internal(client: Client, requested_card: Card, disable_emoji: bool) -> str: mana = emoji.replace_emoji(''.join(requested_card.mana_cost or []), client) legal = ' — ' + emoji.legal_emoji(requested_card, True) if disable_emoji: legal = '' if requested_card.get('mode', None) == '$': text = '{name} {legal} — {price}'.format( name=requested_card.name, price=fetcher.card_price_string(requested_card), legal=legal) else: text = '{name} {mana} — {type}{legal}'.format(name=requested_card.name, mana=mana, type=requested_card.type, legal=legal) if requested_card.bugs: for bug in requested_card.bugs: text += '\n:beetle:{rank} bug: {bug}'.format( bug=bug['description'], rank=bug['classification']) if bug['last_confirmed'] < (dtutil.now() - datetime.timedelta(days=60)): time_since_confirmed = (dtutil.now() - bug['last_confirmed']).seconds text += ' (Last confirmed {time} ago.)'.format( time=dtutil.display_time(time_since_confirmed, 1)) return text
def run() -> None: files = rotation.files() n = len(files) time_until = min( TIME_UNTIL_FULL_ROTATION, TIME_UNTIL_SUPPLEMENTAL_ROTATION) - datetime.timedelta(weeks=1) if n >= TOTAL_RUNS: print( 'It is the moment of discovery, the triumph of the mind, and the end of this rotation.' ) return if n == 0 and TIME_UNTIL_FULL_ROTATION > datetime.timedelta( 7) and TIME_UNTIL_SUPPLEMENTAL_ROTATION > datetime.timedelta(7): print( 'The monks of the North Tree rarely saw their kodama until the rotation, when it woke like a slumbering, angry bear.' ) print('ETA: {t}'.format( t=dtutil.display_time(time_until.total_seconds()))) return all_prices = {} for url in configuration.get_list('cardhoarder_urls'): s = fetcher_internal.fetch(url) s = ftfy.fix_encoding(s) all_prices[url] = parse_cardhoarder_prices(s) url = configuration.get_str('mtgotraders_url') if url: s = fetcher_internal.fetch(url) all_prices['mtgotraders'] = parse_mtgotraders_prices(s) run_number = process(all_prices) if run_number == TOTAL_RUNS: make_final_list()
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')
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')
async def rotation(self, bot, channel): """`!rotation` Give the date of the next Penny Dreadful rotation.""" next_rotation = rotation.next_rotation() now = dtutil.now() if next_rotation > now: diff = next_rotation - now msg = "The next rotation is in {diff}".format(diff=dtutil.display_time(diff.total_seconds())) await bot.client.send_message(channel, msg)
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')
def last_switcheroo(self) -> str: last_switcheroo = stats.calc_last_switcheroo() if last_switcheroo: start = last_switcheroo.start_time_aware() diff = -1 if start is not None: diff = dtutil.dt2ts(dtutil.now()) - dtutil.dt2ts(start) return dtutil.display_time(diff) return 'unknown'
def rotation_api(): now = dtutil.now() diff = rotation.next_rotation() - now result = { "last": rotation.last_rotation_ex(), "next": rotation.next_rotation_ex(), "diff": diff.total_seconds(), "friendly_diff": dtutil.display_time(diff.total_seconds()) } return return_json(result)
def rotation_api() -> Response: now = dtutil.now() diff = rotation.next_rotation() - now result = { 'last': rotation.last_rotation_ex(), 'next': rotation.next_rotation_ex(), 'diff': diff.total_seconds(), 'friendly_diff': dtutil.display_time(diff.total_seconds()) } return return_json(result)
async def post_cards(self, cards, channel, replying_to=None, additional_text=''): await self.client.send_typing(channel) not_pd = configuration.get('not_pd').split(',') disable_emoji = False if channel.id in not_pd: # or (channel.server and channel.server.id in not_pd): disable_emoji = True if len(cards) == 0: if replying_to is not None: text = '{author}: No matches.'.format(author=replying_to.mention) else: text = 'No matches.' await self.client.send_message(channel, text) return cards = command.uniqify_cards(cards) more_text = '' if len(cards) > 10: more_text = ' and ' + str(len(cards) - 4) + ' more.' cards = cards[:4] if len(cards) == 1: card = cards[0] mana = emoji.replace_emoji(''.join(card.mana_cost or []), self.client) legal = ' — ' + emoji.legal_emoji(card, True) if disable_emoji: legal = '' text = '{name} {mana} — {type}{legal}'.format(name=card.name, mana=mana, type=card.type, legal=legal) if card.bug_desc is not None: text += '\n:beetle:{rank} bug: {bug}'.format(bug=card.bug_desc, rank=card.bug_class) now_ts = dtutil.dt2ts(dtutil.now()) if card.bug_last_confirmed < now_ts - 60 * 60 * 24 * 60: text += ' (Last confirmed {time} ago.)'.format(time=dtutil.display_time(now_ts - card.bug_last_confirmed, 1)) else: text = ', '.join('{name} {legal}'.format(name=card.name, legal=(emoji.legal_emoji(card)) if not disable_emoji else '') for card in cards) text += more_text if len(cards) > 10: image_file = None else: image_file = image_fetcher.download_image(cards) if image_file is None: text += '\n\n' if len(cards) == 1: text += emoji.replace_emoji(cards[0].text, self.client) else: text += 'No image available.' text += '\n' + additional_text if image_file is None: await self.client.send_message(channel, text) else: message = await self.client.send_file(channel, image_file, content=text) if message and message.attachments and message.attachments[0]['size'] == 0: print('Message size is zero so resending') await self.client.delete_message(message) await self.client.send_file(channel, image_file, content=text)
def tournament_info(time_direction: TimeDirection, units: int = 2) -> Dict[str, Any]: day, time = get_nearest_tournament(time_direction) next_tournament_time_precise = abs(dtutil.dt2ts(time) - dtutil.dt2ts(dtutil.now())) near = next_tournament_time_precise < 18000 # Threshold for near: 5 hours in seconds next_tournament_time = dtutil.display_time(next_tournament_time_precise, units) return { 'next_tournament_name': 'Penny Dreadful {day}'.format(day=day), 'next_tournament_time': next_tournament_time, 'next_tournament_time_precise': next_tournament_time_precise, 'near': near }
def tournament_info(time_direction: TimeDirection, units: int = 2) -> Dict[str, Any]: tournament_id, name, time = get_nearest_tournament(time_direction) next_tournament_time_precise = abs(dtutil.dt2ts(time) - dtutil.dt2ts(dtutil.now())) near = next_tournament_time_precise < 18000 # Threshold for near: 5 hours in seconds next_tournament_time = dtutil.display_time(next_tournament_time_precise, units) info = { 'next_tournament_name': name, 'next_tournament_time': next_tournament_time, 'next_tournament_time_precise': next_tournament_time_precise, 'near': near, } info.update(series_info(tournament_id)) return info
async def tournament(ctx: MtgContext) -> None: """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 ctx.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/')))
def price_info(c: Card) -> str: try: p = card_price(c.name) except FetchException: return 'Price unavailable' if p is None: return 'Not available online' # Currently disabled s = '{price}'.format(price=format_price(p['price'])) if float(p['low']) <= 0.05: s += ' (low {low}, high {high}'.format(low=format_price(p['low']), high=format_price(p['high'])) if float(p['low']) <= 0.01 and not short: s += ', {week}% this week, {month}% this month, {season}% this season'.format(week=round(float(p['week']) * 100.0), month=round(float(p['month']) * 100.0), season=round(float(p['season']) * 100.0)) s += ')' age = dtutil.dt2ts(dtutil.now()) - p['time'] if age > 60 * 60 * 2: s += '\nWARNING: price information is {display} old'.format(display=dtutil.display_time(age, 1)) return s
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)
def run() -> None: files = rotation.files() n = len(files) time_until = TIME_UNTIL_ROTATION - datetime.timedelta(weeks=1) if n >= rotation.TOTAL_RUNS: print( 'It is the moment of discovery, the triumph of the mind, and the end of this rotation.' ) return if n == 0 and TIME_UNTIL_ROTATION > datetime.timedelta(7): print( 'The monks of the North Tree rarely saw their kodama until the rotation, when it woke like a slumbering, angry bear.' ) print('ETA: {t}'.format( t=dtutil.display_time(int(time_until.total_seconds())))) return if n == 0: rotation.clear_redis(clear_files=True) #else: # rotation.clear_redis() all_prices = {} for url in configuration.get_list('cardhoarder_urls'): s = fetch_tools.fetch(url) s = ftfy.fix_encoding(s) all_prices[url] = parse_cardhoarder_prices(s) url = configuration.get_str('mtgotraders_url') if url: s = fetch_tools.fetch(url) all_prices['mtgotraders'] = parse_mtgotraders_prices(s) run_number = process(all_prices) if run_number == rotation.TOTAL_RUNS: make_final_list() try: url = f'{fetcher.decksite_url()}/api/rotation/clear_cache' fetch_tools.fetch(url) except Exception as c: # pylint: disable=broad-except print(c)
def person_status() -> Response: username = auth.mtgo_username() r = { 'mtgo_username': username, 'discord_id': auth.discord_id(), 'admin': session.get('admin', False), 'demimod': session.get('demimod', False), 'hide_intro': request.cookies.get('hide_intro', False) or auth.hide_intro() or username or auth.discord_id(), 'in_guild': session.get('in_guild', False), } if username: d = guarantee_at_most_one_or_retire(league.active_decks_by(username)) if d is not None: r['deck'] = {'name': d.name, 'url': url_for('deck', deck_id=d.id), 'wins': d.get('wins', 0), 'losses': d.get('losses', 0)} # type: ignore if r['admin'] or r['demimod']: r['archetypes_to_tag'] = len(deck.load_decks('NOT d.reviewed')) active_league = league.active_league() if active_league: time_until_league_end = active_league.end_date - datetime.datetime.now(tz=datetime.timezone.utc) if time_until_league_end <= datetime.timedelta(days=2): r['league_end'] = dtutil.display_time(time_until_league_end/datetime.timedelta(seconds=1), granularity=2) return return_json(r)
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/')))
def message() -> str: diff = next_rotation() - dtutil.now() s = dtutil.display_time(int(diff.total_seconds())) return f'The next rotation is in {s}'