def person_api(person): try: p = ps.load_person(person) p.decks_url = url_for('person_decks_api', person=person) p.head_to_head = url_for('person_h2h_api', person=person) return return_json(p) except DoesNotExistException: return return_json(generate_error('NOTFOUND', 'Person does not exist'))
def random_deck_api() -> Response: blob = league.random_legal_deck() if blob is None: return return_json({ 'error': True, 'msg': 'No legal decks could be found' }) blob['url'] = url_for('deck', deck_id=blob['id'], _external=True) return return_json(blob)
def person_api(person: str, season_id: int = -1) -> Response: if season_id == -1: season_id = rotation.current_season_num() try: p = ps.load_person_by_discord_id_or_username(person, season_id) p.decks_url = url_for('person_decks_api', person=person, season_id=season_id) p.head_to_head = url_for('person_h2h_api', person=person, season_id=season_id) return return_json(p) except DoesNotExistException: return return_json(generate_error('NOTFOUND', 'Person does not exist'))
def search() -> Response: init_search_cache() q = request.args.get('q', '').lower() results: List[SearchItem] = [] if len(q) < 2: return return_json(results) for item in SEARCH_CACHE: if q in item['name'].lower(): results.append(item) return return_json(results)
def league_run_api(person): decks = league.active_decks_by(person) if len(decks) == 0: return return_json(None) run = guarantee_at_most_one_or_retire(decks) decks = league.active_decks() already_played = [m.opponent_deck_id for m in match.get_matches(run)] run.can_play = [d.person for d in decks if d.person != person and d.id not in already_played] return return_json(run)
def drop(person): error = validate_api_key() if error: return error decks = league.active_decks_by(person) if len(decks) == 0: return return_json(generate_error('NO_ACTIVE_RUN', 'That person does not have an active run')) run = guarantee.exactly_one(decks) league.retire_deck(run) result = {'success':True} return return_json(result)
def league_api() -> Response: lg = league.active_league(should_load_decks=True) pdbot = request.form.get('api_token', None) == configuration.get('pdbot_api_token') if not pdbot: lg.decks = [d for d in lg.decks if not d.is_in_current_run()] return return_json(lg)
def person_decks_api(person: str, season_id: int = 0) -> Response: p = ps.load_person_by_discord_id_or_username(person, season_id=season_id) blob = { 'name': p.name, 'decks': p.decks, } return return_json(blob)
def person_status(): r = { 'mtgo_username': auth.mtgo_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 auth.mtgo_username() or auth.discord_id(), 'in_guild': session.get('in_guild', False), } if auth.mtgo_username(): d = guarantee_at_most_one_or_retire( league.active_decks_by(auth.mtgo_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) } if r['admin'] or r['demimod']: r['archetypes_to_tag'] = len(deck.load_decks('NOT d.reviewed')) return return_json(r)
def person_status() -> Response: r = { 'mtgo_username': session.get('mtgo_username'), 'discord_id': session.get('discord_id'), 'admin': session.get('admin', False), 'hide_intro': request.cookies.get('hide_intro', False), } return return_json(r)
def upload() -> Response: error = validate_api_key() if error: return error match_id = int(request.form['match_id']) if match_id == 219603564: return return_json({'success': True}) # Prevent infinite 500 errors. if request.form.get('lines'): lines = request.form['lines'] importing.import_log(lines.split('\n'), match_id) else: importing.import_from_pdbot(match_id) start_time = int(request.form['start_time_utc']) end_time = int(request.form['end_time_utc']) match.get_match(match_id).set_times(start_time, end_time) return return_json({'success': True})
def all_achievements() -> Response: data = {} data['achievements'] = [{ 'key': a.key, 'title': a.title, 'description': a.description_safe } for a in Achievement.all_achievements] return return_json(data)
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 decks_api() -> Response: """ Grab a slice of results from a 0-indexed resultset of decks. Input: { 'archetypeId': <int?>, 'cardName': <str?>, 'competitionId': <int?>, 'personId': <int?>, 'deckType': <'league'|'tournament'|'all'>, 'page': <int>, 'pageSize': <int>, 'personId': <int?>, 'sortBy': <str>, 'sortOrder': <'ASC'|'DESC'>, 'seasonId': <int|'all'> } Output: { 'page': <int>, 'pages': <int>, 'decks': [<deck>] } """ if not request.args.get('sortBy') and request.args.get('competitionId'): sort_by = 'top8' sort_order = 'ASC' elif not request.args.get('sortBy'): sort_by = 'date' sort_order = 'DESC' else: sort_by = str(request.args.get('sortBy')) sort_order = str(request.args.get('sortOrder')) assert sort_order in ['ASC', 'DESC'] order_by = query.decks_order_by(sort_by, sort_order) page_size = int(request.args.get('pageSize', 20)) page = int(request.args.get('page', 0)) start = page * page_size limit = f'LIMIT {start}, {page_size}' # Don't restrict by season if we're loading something with a date by its id. season_id = 'all' if request.args.get( 'competitionId') else rotation.season_id( str(request.args.get('seasonId')), None) where = query.decks_where(request.args, session.get('person_id')) total = deck.load_decks_count(where=where, season_id=season_id) pages = max(ceil(total / page_size) - 1, 0) # 0-indexed ds = deck.load_decks(where=where, order_by=order_by, limit=limit, season_id=season_id) prepare_decks(ds) r = {'page': page, 'pages': pages, 'decks': ds} resp = return_json(r, camelize=True) resp.set_cookie('page_size', str(page_size)) return resp
def leaderboards_api() -> Response: """ Grab a slice of results from a 0-indexed resultset of leaderboard entries. Input: { 'competitionId': <int?>, 'competitionSeriesId': <int?> 'page': <int>, 'pageSize': <int>, 'sortBy': <str>, 'sortOrder': <'ASC'|'DESC'>, 'seasonId': <int|'all'?>, 'q': <str> } Output: { 'page': <int>, 'objects': [<entry>], 'total': <int> } """ order_by = query.leaderboard_order_by(request.args.get('sortBy'), request.args.get('sortOrder')) page_size = int(request.args.get('pageSize', DEFAULT_LIVE_TABLE_PAGE_SIZE)) page = int(request.args.get('page', 0)) start = page * page_size limit = f'LIMIT {start}, {page_size}' q = request.args.get('q', '').strip() where = query.text_match_where(query.person_query(), q) if q else 'TRUE' try: competition_id = int(request.args.get('competitionId', '')) where += f' AND (c.id = {competition_id})' season_id = None except ValueError: season_id = seasons.season_id(str(request.args.get('seasonId')), None) try: competition_series_id = int(request.args.get('competitionSeriesId', '')) where += f' AND (cs.id = {competition_series_id})' except ValueError: pass entries = comp.load_leaderboard(where=where, group_by='p.id', order_by=order_by, limit=limit, season_id=season_id) prepare_leaderboard(entries) total = comp.load_leaderboard_count(where=where, season_id=season_id) r = {'page': page, 'total': total, 'objects': entries} resp = return_json(r, camelize=True) resp.set_cookie('page_size', str(page_size)) return resp
def upload() -> Response: error = validate_api_key() if error: return error match_id = int(request.form['match_id']) lines = request.form['lines'] importing.import_log(lines.split('\n'), match_id) start_time = int(request.form['start_time_utc']) end_time = int(request.form['end_time_utc']) match.get_match(match_id).set_times(start_time, end_time) return return_json({'success': True})
def competitions_api() -> Response: # Don't send competitions with any decks that do not have their correct archetype to third parties otherwise they # will store it and be wrong forever. comps = comp.load_competitions(having='num_reviewed = num_decks', should_load_decks=True) r = [] for c in comps: if c.decks: cr = {} cr['id'] = c.id cr['name'] = c.name cr['url'] = url_for('competition_api', competition_id=c.id, _external=True) r.append(cr) return return_json(r) # type: ignore
def matches_api() -> Response: """ Grab a slice of results from a 0-indexed resultset of matches. Input: { 'competitionId': <int?>, 'page': <int>, 'pageSize': <int>, 'q': <str>, 'sortBy': <str>, 'sortOrder': <'ASC'|'DESC'>, 'seasonId': <int|'all'?> } Output: { 'page': <int>, 'objects': [<entry>], 'total': <int> } """ order_by = query.matches_order_by(request.args.get('sortBy'), request.args.get('sortOrder')) page_size = int(request.args.get('pageSize', DEFAULT_LIVE_TABLE_PAGE_SIZE)) page = int(request.args.get('page', 0)) start = page * page_size limit = f'LIMIT {start}, {page_size}' q = request.args.get('q', '').strip() person_where = query.text_match_where(query.person_query(), q) if q else 'TRUE' opponent_where = query.text_match_where(query.person_query('o'), q) if q else 'TRUE' where = f'({person_where} OR {opponent_where})' try: competition_id = int(request.args.get('competitionId', '')) where += f' AND (c.id = {competition_id})' season_id = None except ValueError: season_id = seasons.season_id(str(request.args.get('seasonId')), None) entries = match.load_matches(where=where, order_by=order_by, limit=limit, season_id=season_id, show_active_deck_names=session.get( 'admin', False)) prepare_matches(entries) total = match.load_matches_count(where=where, season_id=season_id) r = {'page': page, 'total': total, 'objects': entries} resp = return_json(r, camelize=True) resp.set_cookie('page_size', str(page_size)) return resp
def deck_embed(deck_id: int) -> Response: # Discord doesn't actually show this yet. I've reached out to them for better documentation about what they do/don't accept. d = deck.load_deck(deck_id) view = DeckEmbed(d, None, None) width = 1200 height = 500 embed = { 'type': 'rich', 'version': '1.0', 'title': view.page_title(), 'width': width, 'height': height, 'html': template.render(view) } return return_json(embed)
def h2h_api() -> Response: """ Grab a slice of results from a 0-indexed resultset of head-to-head entries. Input: { 'page': <int>, 'pageSize': <int>, 'personId': <int>, 'sortBy': <str>, 'sortOrder': <'ASC'|'DESC'>, 'seasonId': <int|'all'>, 'q': <str> } Output: { 'page': <int>, 'objects': [<entry>], 'total': <int> } """ order_by = query.head_to_head_order_by(request.args.get('sortBy'), request.args.get('sortOrder')) page_size = int(request.args.get('pageSize', DEFAULT_LIVE_TABLE_PAGE_SIZE)) page = int(request.args.get('page', 0)) start = page * page_size limit = f'LIMIT {start}, {page_size}' season_id = seasons.season_id(str(request.args.get('seasonId')), None) person_id = int(request.args.get('personId', 0)) q = request.args.get('q', '').strip() where = query.text_match_where('opp.mtgo_username', q) if q else 'TRUE' entries = ps.load_head_to_head(person_id, where=where, order_by=order_by, limit=limit, season_id=season_id) for entry in entries: entry.opp_url = url_for('.person', mtgo_username=entry.opp_mtgo_username, season_id=None if season_id == seasons.current_season_num() else season_id) total = ps.load_head_to_head_count(person_id=person_id, where=where, season_id=season_id) r = {'page': page, 'total': total, 'objects': entries} resp = return_json(r, camelize=True) resp.set_cookie('page_size', str(page_size)) return resp
def cards2_api() -> Response: """ Grab a slice of results from a 0-indexed resultset of cards. Input: { 'deckType': <'league'|'tournament'|'all'>, 'page': <int>, 'pageSize': <int>, 'personId': <int?>, 'sortBy': <str>, 'sortOrder': <'ASC'|'DESC'>, 'seasonId': <int|'all'>, 'q': <str> } Output: { 'page': <int>, 'objects': [<card>], 'total': <int> } """ order_by = query.cards_order_by(request.args.get('sortBy'), request.args.get('sortOrder')) page_size = int(request.args.get('pageSize', DEFAULT_LIVE_TABLE_PAGE_SIZE)) page = int(request.args.get('page', 0)) start = page * page_size limit = f'LIMIT {start}, {page_size}' person_id = request.args.get('personId') or None tournament_only = request.args.get('deckType') == 'tournament' season_id = seasons.season_id(str(request.args.get('seasonId')), None) q = request.args.get('q', '').strip() additional_where = query.card_search_where(q) if q else 'TRUE' cs = card.load_cards(additional_where=additional_where, order_by=order_by, limit=limit, person_id=person_id, tournament_only=tournament_only, season_id=season_id) prepare_cards(cs, tournament_only=tournament_only) total = card.load_cards_count(additional_where=additional_where, person_id=person_id, season_id=season_id) r = {'page': page, 'total': total, 'objects': cs} resp = return_json(r, camelize=True) resp.set_cookie('page_size', str(page_size)) return resp
def rotation_cards_api() -> Response: """ Grab a slice of results from a 0-indexed resultset of cards that are potentially rotating in. Input: { 'page': <int>, 'pageSize': <int>, 'q': <str>, 'sortBy': <str>, 'sortOrder': <'ASC'|'DESC'> } Output: { 'page': <int>, 'objects': [<entry>], 'total': <int> } """ _, _, cs = rotation.read_rotation_files() q = request.args.get('q', '').lower() search_results = None try: search_results = [c.name for c in card_search.search(q)] if q else None except card_search.InvalidSearchException: pass if search_results is not None: cs = [c for c in cs if c.name in search_results] if not session.get('admin', False): cs = [c for c in cs if c.status != 'Undecided'] total = len(cs) # Now add interestingness to the cards, which only decksite knows not magic.rotation. playability = card.playability() for c in cs: c.interestingness = rotation.interesting(playability, c) rotation.rotation_sort(cs, request.args.get('sortBy'), request.args.get('sortOrder')) page_size = int(request.args.get('pageSize', DEFAULT_LIVE_TABLE_PAGE_SIZE)) page = int(request.args.get('page', 0)) start = page * page_size end = start + page_size cs = cs[start:end] prepare_cards(cs) r = {'page': page, 'total': total, 'objects': cs} resp = return_json(r, camelize=True) resp.set_cookie('page_size', str(page_size)) return resp
def decks_api() -> Response: """ Grab a slice of results from a 0-indexed resultset of decks. Input: { 'archetypeId': <int?>, 'cardName': <str?>, 'competitionId': <int?>, 'deckType': <'league'|'tournament'|'all'>, 'page': <int>, 'pageSize': <int>, 'personId': <int?>, 'sortBy': <str>, 'sortOrder': <'ASC'|'DESC'>, 'seasonId': <int|'all'> } Output: { 'page': <int>, 'objects': [<deck>] 'total': <int> } """ order_by = query.decks_order_by(request.args.get('sortBy'), request.args.get('sortOrder'), request.args.get('competitionId')) page_size = int(request.args.get('pageSize', DEFAULT_LIVE_TABLE_PAGE_SIZE)) page = int(request.args.get('page', 0)) start = page * page_size limit = f'LIMIT {start}, {page_size}' # Don't restrict by season if we're loading something with a date by its id. season_id = 'all' if request.args.get( 'competitionId') else seasons.season_id( str(request.args.get('seasonId')), None) where = query.decks_where(request.args, session.get('admin'), session.get('person_id')) total = deck.load_decks_count(where=where, season_id=season_id) ds = deck.load_decks(where=where, order_by=order_by, limit=limit, season_id=season_id) prepare_decks(ds) r = {'page': page, 'total': total, 'objects': ds} resp = return_json(r, camelize=True) resp.set_cookie('page_size', str(page_size)) return resp
def person_status(): r = { 'mtgo_username': auth.mtgo_username(), 'discord_id': auth.discord_id(), 'admin': session.get('admin', False) } if auth.mtgo_username(): d = guarantee_at_most_one_or_retire( league.active_decks_by(auth.mtgo_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) } return return_json(r)
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)
def people_api() -> Response: """ Grab a slice of results from a 0-indexed resultset of people. Input: { 'page': <int>, 'pageSize': <int>, 'sortBy': <str>, 'sortOrder': <'ASC'|'DESC'>, 'seasonId': <int|'all'>, 'q': <str> } Output: { 'page': <int>, 'objects': [<person>], 'total': <int> } """ order_by = query.people_order_by(request.args.get('sortBy'), request.args.get('sortOrder')) page_size = int(request.args.get('pageSize', DEFAULT_LIVE_TABLE_PAGE_SIZE)) page = int(request.args.get('page', 0)) start = page * page_size limit = f'LIMIT {start}, {page_size}' season_id = seasons.season_id(str(request.args.get('seasonId')), None) q = request.args.get('q', '').strip() where = query.text_match_where(query.person_query(), q) if q else 'TRUE' people = ps.load_people(where=where, order_by=order_by, limit=limit, season_id=season_id) prepare_people(people) total = ps.load_people_count(where=where, season_id=season_id) r = {'page': page, 'total': total, 'objects': people} resp = return_json(r, camelize=True) resp.set_cookie('page_size', str(page_size)) return resp
def post_rule_update(rule_id: int = None) -> Response: if rule_id is not None and request.form.get('include') is not None and request.form.get('exclude') is not None: inc = [] exc = [] for line in cast(str, request.form.get('include')).strip().splitlines(): try: inc.append(parse_line(line)) except InvalidDataException: return return_json({'success':False, 'msg':f"Couldn't find a card count and name on line: {line}"}) if not cs.card_exists(inc[-1][1]): return return_json({'success':False, 'msg':f'Card not found in any deck: {line}'}) for line in cast(str, request.form.get('exclude')).strip().splitlines(): try: exc.append(parse_line(line)) except InvalidDataException: return return_json({'success':False, 'msg':f"Couldn't find a card count and name on line: {line}"}) if not cs.card_exists(exc[-1][1]): return return_json({'success':False, 'msg':f'Card not found in any deck {line}'}) rs.update_cards(rule_id, inc, exc) return return_json({'success':True}) return return_json({'success':False, 'msg':'Required keys not found'})
def deck_api(deck_id: int) -> Response: blob = deck.load_deck(deck_id) return return_json(blob)
def test_500() -> Response: if configuration.get_bool('production'): return return_json(generate_error( 'ON_PROD', 'This only works on test environments'), status=404) raise TooManyItemsException()
def person_notes(person_id: int) -> Response: return return_json({'notes': ps.load_notes(person_id)})