def __init__(self) -> None: super().__init__() self.playability = card.playability() until_full_rotation = rotation.next_rotation() - dtutil.now() until_supplemental_rotation = rotation.next_supplemental( ) - dtutil.now() in_rotation = False if until_full_rotation < datetime.timedelta(7): in_rotation = True self.rotation_msg = 'Full rotation is in progress, ends ' + dtutil.display_date( rotation.next_rotation(), 2) elif until_supplemental_rotation < datetime.timedelta(7): in_rotation = True self.rotation_msg = 'Supplemental rotation is in progress, ends ' + dtutil.display_date( rotation.next_supplemental(), 2) elif until_full_rotation < until_supplemental_rotation: self.rotation_msg = 'Full rotation is ' + dtutil.display_date( rotation.next_rotation(), 2) else: self.rotation_msg = 'Supplemental rotation is ' + dtutil.display_date( rotation.next_supplemental(), 2) self.cards: List[Card] = [] if in_rotation: self.read_rotation_files() self.show_interesting = True
def __init__(self, interestingness: Optional[str] = None, rotation_query: Optional[str] = None, only_these: Optional[List[str]] = None) -> None: super().__init__() self.playability = card.playability() until_full_rotation = rotation.next_rotation() - dtutil.now() until_supplemental_rotation = rotation.next_supplemental() - dtutil.now() in_rotation = configuration.get_bool('always_show_rotation') if until_full_rotation < datetime.timedelta(7): in_rotation = True self.rotation_msg = 'Full rotation is in progress, ends ' + dtutil.display_date(rotation.next_rotation(), 2) elif until_supplemental_rotation < datetime.timedelta(7): in_rotation = True self.rotation_msg = 'Supplemental rotation is in progress, ends ' + dtutil.display_date(rotation.next_supplemental(), 2) elif until_full_rotation < until_supplemental_rotation: self.rotation_msg = 'Full rotation is ' + dtutil.display_date(rotation.next_rotation(), 2) else: self.rotation_msg = 'Supplemental rotation is ' + dtutil.display_date(rotation.next_supplemental(), 2) self.cards: List[Card] = [] if in_rotation: self.read_rotation_files() self.show_interesting = True if interestingness: self.cards = [c for c in self.cards if c.get('interestingness') == interestingness] if only_these: self.cards = [c for c in self.cards if c.name in only_these] self.num_cards = len(self.cards) self.rotation_query = rotation_query or ''
def determine_end_of_league(start_date): if start_date.day < 15: month = start_date.month + 1 else: month = start_date.month + 2 if month > 12: year = start_date.year + 1 month = month - 12 else: year = start_date.year end_date_s = '{year}-{month}-01 00:00:00'.format(year=year, month=month) end_date = dtutil.parse(end_date_s, '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ) if end_date > rotation.next_rotation(): end_date = rotation.next_rotation() return end_date - datetime.timedelta(seconds=1)
async def background_task_rotation_hype(self) -> None: rotation_hype_channel_id = configuration.get_int( 'rotation_hype_channel_id') if not rotation_hype_channel_id: logging.warning('rotation hype channel is not configured') return channel = self.get_channel(rotation_hype_channel_id) if not isinstance(channel, discord.abc.Messageable): logging.warning('rotation hype channel is not a text channel') return while self.is_ready(): until_rotation = rotation.next_rotation() - dtutil.now() last_run_time = rotation.last_run_time() if until_rotation < datetime.timedelta( 7) and last_run_time is not None: if dtutil.now() - last_run_time < datetime.timedelta( minutes=5): hype = await rotation_hype_message() if hype: await channel.send(hype) timer = 5 * 60 else: timer = int( (until_rotation - datetime.timedelta(7)).total_seconds()) await asyncio.sleep(timer)
def __init__(self): until_full_rotation = rotation.next_rotation() - dtutil.now() until_supplemental_rotation = rotation.next_supplemental() - dtutil.now() in_rotation = False if until_full_rotation < datetime.timedelta(7): in_rotation = True self.rotation_msg = 'Full rotation is in progress, ends ' + dtutil.display_date(rotation.next_rotation(), 2) elif until_supplemental_rotation < datetime.timedelta(7): in_rotation = True self.rotation_msg = 'Supplemental rotation is in progress, ends ' + dtutil.display_date(rotation.next_supplemental(), 2) elif until_full_rotation < until_supplemental_rotation: self.rotation_msg = 'Full rotation is ' + dtutil.display_date(rotation.next_rotation(), 2) else: self.rotation_msg = 'Supplemental rotation is ' + dtutil.display_date(rotation.next_supplemental(), 2) if in_rotation: self.read_rotation_files() self.show_interesting = True
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)
def setup_rotation(self) -> None: self.season_start_display = dtutil.display_date( rotation.last_rotation()) self.season_end_display = dtutil.display_date(rotation.next_rotation()) self.scryfall_url = 'https://scryfall.com/search?q=f%3Apd' self.legal_cards_url = 'http://pdmtgo.com/legal_cards.txt' self.in_rotation = rotation.in_rotation() self.rotation_msg = 'Rotation is in progress.' self.rotation_url = url_for('rotation')
def admin_menu() -> List[Dict[str, str]]: m = [] endpoints = sorted([rule.endpoint for rule in APP.url_map.iter_rules() if 'GET' in rule.methods and rule.rule.startswith('/admin')]) for endpoint in endpoints: name = titlecase.titlecase(endpoint.replace('_', ' ')) if endpoint else 'Admin Home' m.append({'name': name, 'endpoint': endpoint, 'url': url_for(endpoint)}) if (rotation.next_rotation() - dtutil.now()) < datetime.timedelta(7): m.append({'name': gettext('Rotation Tracking'), 'endpoint': 'rotation'}) m.append({'name': gettext('Rotation Speculation'), 'endpoint': 'rotation_speculation'}) return m
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)
def build_menu() -> List[Dict[str, Union[str, Dict[str, str]]]]: archetypes_badge = None archetypes_badge = {'url': url_for('edit_archetypes'), 'text': '', 'badge_class': 'edit_archetypes'} resources_submenu: List[Dict[str, str]] = [] if (rotation.next_rotation() - dtutil.now()) < datetime.timedelta(7) or (rotation.next_supplemental() - dtutil.now()) < datetime.timedelta(7): resources_submenu += [{'name': gettext('Rotation Tracking'), 'url': url_for('rotation')}] resources_submenu += [ {'name': gettext('Rotation Changes'), 'url': url_for('rotation_changes')}, {'name': gettext('Rotation Speculation'), 'url': url_for('rotation_speculation')}, {'name': gettext('Deck Check'), 'url': url_for('deck_check')}, {'name': gettext('Discord Chat'), 'url': 'https://discord.gg/H6EHdHu'}, {'name': gettext('External Links'), 'url': url_for('resources')}, {'name': gettext('Link Accounts'), 'url': url_for('link')}, {'name': gettext('Bugs'), 'url': url_for('bugs')} ] menu = [ {'name': gettext('Metagame'), 'url': url_for('home'), 'badge': archetypes_badge, 'submenu': [ {'name': gettext('Latest Decks'), 'url': url_for('.decks')}, {'name': gettext('Archetypes'), 'url': url_for('archetypes'), 'badge': archetypes_badge}, {'name': gettext('People'), 'url': url_for('people')}, {'name': gettext('Cards'), 'url': url_for('cards')}, {'name': gettext('Past Seasons'), 'url': url_for('seasons')}, {'name': gettext('Matchups'), 'url': url_for('matchups')}, ]}, {'name': gettext('League'), 'url': url_for('league'), 'submenu': [ {'name': gettext('League Info'), 'url': url_for('league')}, {'name': gettext('Sign Up'), 'url': url_for('signup')}, {'name': gettext('Report'), 'url': url_for('report')}, {'name': gettext('Records'), 'url': url_for('current_league')}, {'name': gettext('Retire'), 'url': url_for('retire')}, ]}, {'name': gettext('Competitions'), 'url': url_for('competitions'), 'submenu': [ {'name': gettext('Competition Results'), 'url': url_for('competitions')}, {'name': gettext('Tournament Info'), 'url': url_for('tournaments')}, {'name': gettext('Leaderboards'), 'url': url_for('tournament_leaderboards')}, {'name': gettext('Gatherling'), 'url': 'https://gatherling.one/'}, {'name': gettext('Achievements'), 'url': url_for('achievements')}, {'name': gettext('Hosting'), 'url': url_for('hosting')} ]}, {'name': gettext('Resources'), 'url': url_for('resources'), 'submenu': resources_submenu}, {'name': gettext('About'), 'url': url_for('about'), 'submenu': [ {'name': gettext('What is Penny Dreadful?'), 'url': url_for('about')}, {'name': gettext('About pennydreadfulmagic.com'), 'url': url_for('about_pdm')}, {'name': gettext('FAQs'), 'url': url_for('faqs')}, {'name': gettext('Community Guidelines'), 'url': url_for('community_guidelines')} ]}, {'name': gettext('Admin'), 'admin_only': True, 'url': url_for('admin_home'), 'submenu': admin.admin_menu()} ] for item in menu: item['has_submenu'] = item.get('submenu') is not None item['is_external'] = item.get('url', '').startswith('http') and '://pennydreadfulmagic.com/' not in item['url'] for subitem in item.get('submenu', []): subitem['is_external'] = subitem.get('url', '').startswith('http') and '://pennydreadfulmagic.com/' not in subitem['url'] return menu
def menu(self): archetypes_badge = None if session.get('admin') is True: n = len(deck.load_decks('NOT d.reviewed')) if n > 0: archetypes_badge = { 'url': url_for('edit_archetypes'), 'text': n } menu = [{ 'name': 'Decks', 'url': url_for('home') }, { 'name': 'Competitions', 'url': url_for('competitions') }, { 'name': 'People', 'url': url_for('people') }, { 'name': 'Cards', 'url': url_for('cards') }, { 'name': 'Archetypes', 'url': url_for('archetypes'), 'badge': archetypes_badge }, { 'name': 'Resources', 'url': url_for('resources') }] if (rotation.next_rotation() - dtutil.now()) < datetime.timedelta(7): menu += [{'name': 'Rotation', 'url': url_for('rotation')}] menu += [{ 'name': 'About', 'url': url_for('about') }, { 'name': 'League', 'url': url_for('league'), 'has_submenu': True, 'submenu': [{ 'name': 'Sign Up', 'url': url_for('signup') }, { 'name': 'Report', 'url': url_for('report') }, { 'name': 'Records', 'url': url_for('current_league') }] }] return menu
def build_menu() -> List[Dict[str, Union[str, Dict[str, str]]]]: current_template = (request.endpoint or '').replace('seasons.', '') archetypes_badge = {'endpoint': 'edit_archetypes', 'text': '', 'badge_class': 'edit_archetypes'} resources_submenu: List[Dict[str, str]] = [] if (rotation.next_rotation() - dtutil.now()) < datetime.timedelta(7) or (rotation.next_supplemental() - dtutil.now()) < datetime.timedelta(7): resources_submenu += [{'name': gettext('Rotation Tracking'), 'endpoint': 'rotation'}] resources_submenu += [ {'name': gettext('Rotation Changes'), 'endpoint': 'rotation_changes'}, {'name': gettext('Rotation Speculation'), 'endpoint': 'rotation_speculation'}, {'name': gettext('Deck Check'), 'endpoint': 'deck_check'}, {'name': gettext('Discord Chat'), 'url': 'https://discord.gg/H6EHdHu'}, {'name': gettext('External Links'), 'endpoint': 'resources'}, {'name': gettext('Link Accounts'), 'endpoint': 'link'}, {'name': gettext('Bugs'), 'endpoint': 'bugs'} ] menu = [ {'name': gettext('Metagame'), 'endpoint': 'home', 'badge': archetypes_badge, 'submenu': [ {'name': gettext('Decks'), 'endpoint': '.decks'}, {'name': gettext('Archetypes'), 'endpoint': 'archetypes', 'badge': archetypes_badge}, {'name': gettext('People'), 'endpoint': 'people'}, {'name': gettext('Cards'), 'endpoint': 'cards'}, {'name': gettext('Past Seasons'), 'endpoint': 'seasons'}, {'name': gettext('Matchups'), 'endpoint': 'matchups'}, ]}, {'name': gettext('League'), 'endpoint': 'league', 'submenu': [ {'name': gettext('League Info'), 'endpoint': 'league'}, {'name': gettext('Sign Up'), 'endpoint': 'signup'}, {'name': gettext('Report'), 'endpoint': 'report'}, {'name': gettext('Records'), 'endpoint': 'current_league'}, {'name': gettext('Retire'), 'endpoint': 'retire'}, ]}, {'name': gettext('Competitions'), 'endpoint': 'competitions', 'submenu': [ {'name': gettext('Competition Results'), 'endpoint': 'competitions'}, {'name': gettext('Tournament Info'), 'endpoint': 'tournaments'}, {'name': gettext('Leaderboards'), 'endpoint': 'tournament_leaderboards'}, {'name': gettext('Gatherling'), 'url': 'https://gatherling.com/'}, {'name': gettext('Achievements'), 'endpoint': 'achievements'}, {'name': gettext('Hosting'), 'endpoint': 'hosting'} ]}, {'name': gettext('Resources'), 'endpoint': 'resources', 'submenu': resources_submenu}, {'name': gettext('About'), 'endpoint': 'about', 'submenu': [ {'name': gettext('What is Penny Dreadful?'), 'endpoint': 'about'}, {'name': gettext('About pennydreadfulmagic.com'), 'endpoint': 'about_pdm'}, {'name': gettext('FAQs'), 'endpoint': 'faqs'}, {'name': gettext('Community Guidelines'), 'endpoint': 'community_guidelines'} ]}, {'name': gettext('Admin'), 'admin_only': True, 'endpoint': 'admin_home', 'submenu': admin.admin_menu()} ] setup_links(menu) for item in menu: item['current'] = item.get('endpoint', '').replace('seasons', '').replace('.', '') == current_template or current_template in [entry.get('endpoint', '') for entry in item.get('submenu', [])] item['has_submenu'] = item.get('submenu') is not None return menu
def active_league() -> competition.Competition: where = 'c.id = ({id_query})'.format(id_query=active_competition_id_query()) leagues = competition.load_competitions(where) if len(leagues) == 0: start_date = dtutil.now(tz=dtutil.WOTC_TZ) end_date = determine_end_of_league(start_date, rotation.next_rotation()) name = determine_league_name(start_date, end_date) comp_id = competition.get_or_insert_competition(start_date, end_date, name, 'League', None, competition.Top.EIGHT) if not comp_id: raise InvalidDataException(f'No competition id with {start_date}, {end_date}, {name}') leagues = [competition.load_competition(comp_id)] return guarantee.exactly_one(leagues)
def __init__(self, interestingness: Optional[str] = None, query: Optional[str] = '') -> None: super().__init__() until_full_rotation = rotation.next_rotation() - dtutil.now() until_supplemental_rotation = rotation.next_supplemental() - dtutil.now() in_rotation = configuration.get_bool('always_show_rotation') if until_full_rotation < datetime.timedelta(7): in_rotation = True self.rotation_msg = 'Full rotation is in progress, ends ' + dtutil.display_date(rotation.next_rotation(), 2) elif until_supplemental_rotation < datetime.timedelta(7): in_rotation = True self.rotation_msg = 'Supplemental rotation is in progress, ends ' + dtutil.display_date(rotation.next_supplemental(), 2) elif until_full_rotation < until_supplemental_rotation: self.rotation_msg = 'Full rotation is ' + dtutil.display_date(rotation.next_rotation(), 2) else: self.rotation_msg = 'Supplemental rotation is ' + dtutil.display_date(rotation.next_supplemental(), 2) if in_rotation: self.in_rotation = in_rotation self.show_interestingness_filter = True self.runs, self.runs_percent, self.cards = rotation.read_rotation_files() # Now add interestingness to the cards, which only decksite knows not magic.rotation. playability = card.playability() c: Card for c in self.cards: c.interestingness = rotation.interesting(playability, c) else: self.cards = [] self.show_interesting = True if interestingness: self.cards = [c for c in self.cards if c.get('interestingness') == interestingness] self.num_cards = len(self.cards) self.query = query for c in self.cards: if c.status != 'Undecided': continue c.hits = redact(c.hits) c.hits_needed = redact(c.hits_needed) c.percent = redact(c.percent) c.percent_needed = redact(c.percent_needed) self.show_filters_toggle = True
def next_pd500_date() -> datetime.datetime: end_of_season = rotation.next_rotation() return end_of_season - datetime.timedelta( days=11, hours=13, minutes=30 ) # This effectively hardcodes a 10:30 PD Sat start time AND a Thu/Fri midnight rotation time.
def menu(self): archetypes_badge = None n = len(deck.load_decks('NOT d.reviewed')) if n > 0: archetypes_badge = {'url': url_for('edit_archetypes'), 'text': n} resources_submenu = [] if (rotation.next_rotation() - dtutil.now()) < datetime.timedelta( 7) or (rotation.next_supplemental() - dtutil.now()) < datetime.timedelta(7): resources_submenu += [{ 'name': gettext('Rotation Tracking'), 'url': url_for('rotation') }] resources_submenu += [{ 'name': gettext('Rotation Changes'), 'url': url_for('rotation_changes') }, { 'name': gettext('Rotation Speculation'), 'url': url_for('rotation_speculation') }, { 'name': gettext('Discord Chat'), 'url': 'https://discord.gg/H6EHdHu' }, { 'name': gettext('External Links'), 'url': url_for('resources') }, { 'name': gettext('Log In'), 'url': url_for('authenticate', target=request.url) }, { 'name': gettext('Log Out'), 'url': url_for('logout') }] menu = [{ 'name': gettext('Metagame'), 'url': url_for('home'), 'badge': archetypes_badge, 'submenu': [{ 'name': gettext('Latest Decks'), 'url': url_for('decks') }, { 'name': gettext('Archetypes'), 'url': url_for('archetypes'), 'badge': archetypes_badge }, { 'name': gettext('People'), 'url': url_for('people') }, { 'name': gettext('Cards'), 'url': url_for('cards') }, { 'name': gettext('Past Seasons'), 'url': url_for('seasons') }] }, { 'name': gettext('League'), 'url': url_for('league'), 'submenu': [ { 'name': gettext('League Info'), 'url': url_for('league') }, { 'name': gettext('Sign Up'), 'url': url_for('signup') }, { 'name': gettext('Report'), 'url': url_for('report') }, { 'name': gettext('Records'), 'url': url_for('current_league') }, { 'name': gettext('Retire'), 'url': url_for('retire') }, ] }, { 'name': gettext('Competitions'), 'url': url_for('competitions'), 'submenu': [{ 'name': gettext('Competition Results'), 'url': url_for('competitions') }, { 'name': gettext('Tournament Info'), 'url': url_for('tournaments') }, { 'name': gettext('Leaderboards'), 'url': url_for('tournament_leaderboards') }, { 'name': gettext('Gatherling'), 'url': 'https://gatherling.com/' }, { 'name': gettext('Hosting'), 'url': url_for('hosting') }] }, { 'name': gettext('Resources'), 'url': url_for('resources'), 'submenu': resources_submenu }, { 'name': gettext('About'), 'url': url_for('about'), 'submenu': [{ 'name': gettext('What is Penny Dreadful?'), 'url': url_for('about') }, { 'name': gettext('About pennydreadfulmagic.com'), 'url': url_for('about_pdm') }, { 'name': gettext('FAQs'), 'url': url_for('faqs') }, { 'name': gettext('Community Guidelines'), 'url': url_for('community_guidelines') }] }, { 'name': gettext('Admin'), 'admin_only': True, 'url': url_for('admin'), 'submenu': admin.menu() }] for item in menu: item['has_submenu'] = item.get('submenu') is not None item['is_external'] = item.get('url', '').startswith( 'http') and '://pennydreadfulmagic.com/' not in item['url'] for subitem in item.get('submenu', []): subitem['is_external'] = subitem.get('url', '').startswith( 'http' ) and '://pennydreadfulmagic.com/' not in subitem['url'] return menu
import datetime import fileinput import os import pathlib import shutil import subprocess from collections import Counter from typing import Dict, List, Set import ftfy from magic import card_price, fetcher, rotation from price_grabber.parser import PriceListType, parse_cardhoarder_prices, parse_mtgotraders_prices from shared import configuration, dtutil, fetch_tools, redis, repo, text TIME_UNTIL_ROTATION = rotation.next_rotation() - dtutil.now() 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.'