Exemplo n.º 1
0
def find_matches(d: deck.Deck, rows: ResultSet) -> MatchListType:
    matches = []
    for row in rows:
        tds = row.find_all('td')
        if 'No matches were found for this deck' in tds[0].renderContents().decode('utf-8'):
            logger.warning('Skipping {identifier} because it played no matches.'.format(identifier=d.identifier))
            break
        round_type, num = re.findall(r'([TR])(\d+)', tds[0].string)[0]
        num = int(num)
        if round_type == 'R':
            elimination = 0
            round_num = num
        elif round_type == 'T':
            elimination = num
            round_num += 1
        else:
            raise InvalidDataException('Round was neither Swiss (R) nor Top 4/8 (T) in {round_type} for {id}'.format(round_type=round_type, id=d.id))
        if 'Bye' in tds[1].renderContents().decode('utf-8') or 'No Deck Found' in tds[5].renderContents().decode('utf-8'):
            left_games, right_games, right_identifier = 2, 0, None
        else:
            left_games, right_games = tds[2].string.split(' - ')
            href = tds[5].find('a')['href']
            right_identifier = re.findall(r'id=(\d+)', href)[0]
        matches.append({
            'round': round_num,
            'elimination': elimination,
            'left_games': left_games,
            'left_identifier': d.identifier,
            'right_games': right_games,
            'right_identifier': right_identifier
        })
    return matches
Exemplo n.º 2
0
def setup_session(url: str) -> None:
    discord = make_session(state=session.get('oauth2_state'))
    token = discord.fetch_token(TOKEN_URL,
                                client_secret=OAUTH2_CLIENT_SECRET,
                                authorization_response=url)
    session.permanent = True
    session['oauth2_token'] = token
    discord = make_session(token=session.get('oauth2_token'))
    user = discord.get(API_BASE_URL + '/users/@me').json()
    session['id'] = user['id']
    session['discord_id'] = user['id']
    session['discord_locale'] = user['locale']
    guilds = discord.get(API_BASE_URL + '/users/@me/guilds').json()
    wrong_guilds = False  # protect against an unexpected response from discord
    session['in_guild'] = False
    session['admin'] = False
    session['demimod'] = False
    for guild in guilds:
        if isinstance(guild, dict) and 'id' in guild:
            if guild['id'] == configuration.get('guild_id'):
                session['admin'] = (
                    guild['permissions'] & 0x10000000
                ) != 0  # Check for the MANAGE_ROLES permissions on Discord as a proxy for "is admin".
                session['demimod'] = (
                    guild['permissions'] & 0x20000
                ) != 0  # Check for the "Mention @everyone" permissions on Discord as a proxy for "is demimod".
                session['in_guild'] = True
        else:
            wrong_guilds = True
    if wrong_guilds:
        logger.warning(
            'auth.py: unexpected discord response. Guilds: {g}'.format(
                g=guilds))
Exemplo n.º 3
0
def run() -> None:
    sql = """
        SELECT
            GROUP_CONCAT(d.person_id) AS people,
            GROUP_CONCAT(dm.games) AS games
        FROM
            `match` AS m
        INNER JOIN
            deck_match AS dm ON dm.match_id = m.id
        INNER JOIN
            deck AS d ON dm.deck_id = d.id
        GROUP BY
            m.id
        ORDER BY
            m.date,
            `round`
    """
    matches = db().select(sql)
    for m in matches:
        match(m)
    current = person.load_people()
    people_by_id = {p.id: p for p in current}
    sql = 'UPDATE person SET elo = %s WHERE id = %s'
    for person_id, new_elo in sorted(PEOPLE.items(), key=lambda x: -x[1]):
        p = people_by_id[int(person_id)]
        if p.elo != new_elo:
            logger.warning(
                '{id} currently has Elo of {current_elo} and we are setting it to {new_elo}'
                .format(id=p.id, current_elo=p.elo, new_elo=new_elo))
            db().execute(sql, [new_elo, p.id])
Exemplo n.º 4
0
def tournament_deck(cells: ResultSet, competition_id: int,
                    date: datetime.datetime,
                    final: Dict[str, int]) -> Optional[deck.Deck]:
    d: deck.RawDeckDescription = {
        'source': 'Gatherling',
        'competition_id': competition_id,
        'created_date': dtutil.dt2ts(date)
    }
    player = cells[2]
    username = aliased(player.a.contents[0].string)
    d['mtgo_username'] = username
    d['finish'] = final.get(username)
    link = cells[4].a
    d['url'] = gatherling_url(link['href'])
    d['name'] = link.string
    if cells[5].find('a'):
        d['archetype'] = cells[5].a.string
    else:
        d['archetype'] = cells[5].string
    gatherling_id = urllib.parse.parse_qs(
        urllib.parse.urlparse(str(d['url'])).query)['id'][0]
    d['identifier'] = gatherling_id
    existing = deck.get_deck_id(d['source'], d['identifier'])
    if existing is not None:
        return deck.load_deck(existing)
    dlist = decklist.parse(
        fetcher.internal.post(gatherling_url('deckdl.php'),
                              {'id': gatherling_id}))
    d['cards'] = dlist
    if len(dlist['maindeck']) + len(dlist['sideboard']) == 0:
        logger.warning(
            'Rejecting deck with id {id} because it has no cards.'.format(
                id=gatherling_id))
        return None
    return deck.add_deck(d)
Exemplo n.º 5
0
def squash(p1id: int, p2id: int, col1: str, col2: str) -> None:
    logger.warning('Squashing {p1id} and {p2id} on {col1} and {col2}'.format(p1id=p1id, p2id=p2id, col1=col1, col2=col2))
    db().begin()
    new_value = db().value('SELECT {col2} FROM person WHERE id = %s'.format(col2=col2), [p2id])
    db().execute('UPDATE deck SET person_id = %s WHERE person_id = %s', [p1id, p2id])
    db().execute('DELETE FROM person WHERE id = %s', [p2id])
    db().execute('UPDATE person SET {col2} = %s WHERE id = %s'.format(col2=col2), [new_value, p1id])
    db().commit()
Exemplo n.º 6
0
def acceptable_file(path: str) -> bool:
    if not os.path.exists(path):
        return False
    if os.path.getsize(
            path
    ) >= 6860:  # This is a few bytes smaller than a completely empty graph on prod.
        return True
    logger.warning('Chart at {path} is suspiciously small.'.format(path=path))
    return False
Exemplo n.º 7
0
 def __init_subclass__(cls) -> None:
     if cls.key is not None:
         # in case anyone ever makes a poor sportsmanship achievement called DROP TABLE
         cls.key = re.sub('[^A-Za-z0-9_]+', '', cls.key)
         if cls.key in [c.key for c in cls.all_achievements]:
             logger.warning(f"Two achievements have the same normalised key {cls.key}. This won't do any permanent damage to the database but the results are almost certainly not as intended.")
         # pylint can't determine that we have verified cls.key to be a str
         if cls.key[-7:] == '_detail': # pylint: disable=unsubscriptable-object
             logger.warning(f"Achievement key {cls.key} should not end with the string '_detail'.")
         cls.all_achievements.append(cls())
Exemplo n.º 8
0
def scrape() -> None:
    login()
    logger.warning('Logged in to TappedOut: {is_authorised}'.format(is_authorised=is_authorised()))
    raw_decks = fetch_decks()
    for raw_deck in raw_decks:
        try:
            if is_authorised():
                raw_deck.update(fetch_deck_details(raw_deck))
            raw_deck = set_values(raw_deck)
            deck.add_deck(raw_deck)
        except InvalidDataException as e:
            logger.warning('Skipping {slug} because of {e}'.format(slug=raw_deck.get('slug', '-no slug-'), e=e))
Exemplo n.º 9
0
def add_cards(deck_id: int, cards: CardsDescription) -> None:
    try:
        db().begin('add_cards')
        for name, n in cards.get('maindeck', {}).items():
            insert_deck_card(deck_id, name, n, False)
        for name, n in cards.get('sideboard', {}).items():
            insert_deck_card(deck_id, name, n, True)
        db().commit('add_cards')
    except InvalidDataException as e:
        logger.warning('Unable to add_cards to {deck_id} with {cards}', e)
        db().rollback('add_cards')
        raise
Exemplo n.º 10
0
def tournament_matches(d: deck.Deck) -> List[bs4.element.Tag]:
    url = 'https://gatherling.com/deck.php?mode=view&id={identifier}'.format(identifier=d.identifier)
    s = fetcher.internal.fetch(url, character_encoding='utf-8')
    soup = BeautifulSoup(s, 'html.parser')
    anchor = soup.find(string='MATCHUPS')
    if anchor is None:
        logger.warning('Skipping {id} because it has no MATCHUPS.'.format(id=d.id))
        return []
    table = anchor.findParents('table')[0]
    rows = table.find_all('tr')
    rows.pop(0) # skip header
    rows.pop() # skip empty last row
    return find_matches(d, rows)
Exemplo n.º 11
0
def add_cards(deck_id: int, cards: CardsDescription) -> None:
    try:
        db().begin('add_cards')
        deckhash = hashlib.sha1(repr(cards).encode('utf-8')).hexdigest()
        db().execute('UPDATE deck SET decklist_hash = %s WHERE id = %s', [deckhash, deck_id])
        db().execute('DELETE FROM deck_card WHERE deck_id = %s', [deck_id])
        for name, n in cards['maindeck'].items():
            insert_deck_card(deck_id, name, n, False)
        for name, n in cards['sideboard'].items():
            insert_deck_card(deck_id, name, n, True)
        db().commit('add_cards')
    except InvalidDataException as e:
        logger.warning('Unable to add_cards to {deck_id} with {cards}', e)
        db().rollback('add_deck')
        raise
Exemplo n.º 12
0
def preaggregate(table: str, sql: str) -> None:
    lock_key = f'preaggregation:{table}'
    try:
        db().get_lock(lock_key, 60 * 5)
    except DatabaseException as e:
        logger.warning(f'Not preaggregating {table} because of {e}')
    db().execute(f'DROP TABLE IF EXISTS _new{table}')
    db().execute(sql)
    db().execute(f'DROP TABLE IF EXISTS _old{table}')
    db().execute(f'CREATE TABLE IF NOT EXISTS {table} (_ INT)'
                 )  # Prevent error in RENAME TABLE below if bootstrapping.
    db().execute(
        f'RENAME TABLE {table} TO _old{table}, _new{table} TO {table}')
    db().execute(f'DROP TABLE IF EXISTS _old{table}')
    db().release_lock(lock_key)
Exemplo n.º 13
0
def cache_all_rules() -> None:
    table = '_applied_rules'
    logger.warning(f'Creating {table}')
    sql = """
            CREATE TABLE IF NOT EXISTS _new{table} (
                deck_id INT NOT NULL,
                rule_id INT NOT NULL,
                archetype_id INT NOT NULL,
                archetype_name TEXT,
                PRIMARY KEY (deck_id, rule_id),
                FOREIGN KEY (deck_id) REFERENCES deck (id) ON UPDATE CASCADE ON DELETE CASCADE,
                FOREIGN KEY (rule_id) REFERENCES rule (id) ON UPDATE CASCADE ON DELETE CASCADE,
                FOREIGN KEY (archetype_id) REFERENCES archetype (id) ON UPDATE CASCADE ON DELETE CASCADE
            ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci AS
            {apply_rules_query}
        """.format(table=table, apply_rules_query=apply_rules_query(deck_query=classified_decks_query()))
    preaggregation.preaggregate(table, sql)
    logger.warning(f'Done creating {table}')
Exemplo n.º 14
0
def setup() -> None:
    from decksite import APP
    with APP.app_context():
        db().execute('CREATE TABLE IF NOT EXISTS db_version (version INTEGER UNIQUE NOT NULL)')
        version = db_version()
        patches = os.listdir('decksite/sql')
        patches.sort(key=lambda n: int(n.split('.')[0]))
        for fn in patches:
            path = os.path.join('decksite/sql', fn)
            n = int(fn.split('.')[0])
            if version < n:
                logger.warning('Patching database to v{0}'.format(n))
                fh = open(path, 'r')
                sql = fh.read()
                for stmt in sql.split(';'):
                    if stmt.strip() != '':
                        db().execute(stmt)
                fh.close()
                db().execute('INSERT INTO db_version (version) VALUES ({n})'.format(n=n))
Exemplo n.º 15
0
def scrape(limit: int = 255) -> None:
    page = 1
    while page <= limit:
        time.sleep(0.1)
        url = 'https://www.mtggoldfish.com/deck/custom/penny_dreadful?page={n}#online'.format(
            n=page)
        soup = BeautifulSoup(
            fetcher.internal.fetch(url, character_encoding='utf-8'),
            'html.parser')
        raw_decks = soup.find_all('div', {'class': 'deck-tile'})
        if len(raw_decks) == 0:
            logger.warning(
                'No decks found in {url} so stopping.'.format(url=url))
            break
        for raw_deck in raw_decks:
            d = Container({'source': 'MTG Goldfish'})
            a = raw_deck.select_one('h2 > span.deck-price-online > a')
            d.identifier = re.findall(r'/deck/(\d+)#online', a.get('href'))[0]
            d.url = 'https://www.mtggoldfish.com/deck/{identifier}#online'.format(
                identifier=d.identifier)
            d.name = a.contents[0].strip()
            d.mtggoldfish_username = raw_deck.select_one(
                'div.deck-tile-author').contents[0].strip()
            remove_by = re.match(r'^(by )?(.*)$', d.mtggoldfish_username)
            if remove_by:
                d.mtggoldfish_username = remove_by.group(2)
            d.created_date = scrape_created_date(d)
            time.sleep(1)
            d.cards = scrape_decklist(d)
            try:
                vivified = decklist.vivify(d.cards)
            # MTGG doesn't do any validation of cards so some decks with fail here with card names like 'Stroke of Genuineness'.
            except InvalidDataException as e:
                logger.warning(
                    'Rejecting decklist of deck with identifier {identifier} because of {e}'
                    .format(identifier=d.identifier, e=e))
                continue
            if len([
                    f for f in legality.legal_formats(vivified)
                    if 'Penny Dreadful' in f
            ]) == 0:
                logger.warning(
                    'Rejecting deck with identifier {identifier} because it is not legal in any PD formats.'
                    .format(identifier=d.identifier))
                continue
            if len(d.cards) == 0:
                logger.warning(
                    'Rejecting deck with identifier {identifier} because it has no cards.'
                    .format(identifier=d.identifier))
                continue
            deck.add_deck(d)
        page += 1
Exemplo n.º 16
0
def report(form: ReportForm) -> bool:
    try:
        db().get_lock('deck_id:{id}'.format(id=form.entry))
        db().get_lock('deck_id:{id}'.format(id=form.opponent))

        for m in match.get_matches(form):
            if int(form.opponent) == m.opponent_deck_id:
                form.errors['result'] = 'This match was reported as You {game_wins}–{game_losses} {opponent} {date}'.format(game_wins=m.game_wins, game_losses=m.game_losses, opponent=m.opponent, date=dtutil.display_date(m.date))
                return False

        counts = deck.count_matches(form.entry, form.opponent)
        if counts[int(form.entry)] >= 5:
            form.errors['entry'] = 'You already have 5 matches reported'
            return False
        if counts[int(form.opponent)] >= 5:
            form.errors['opponent'] = 'Your opponent already has 5 matches reported'
            return False
        pdbot = form.get('api_token', None) == configuration.get('pdbot_api_token')
        if pdbot:
            mtgo_match_id = form.get('matchID', None)
        else:
            mtgo_match_id = None
            entry_name = deck.load_deck(int(form.entry)).person
            opp_name = deck.load_deck(int(form.opponent)).person
            if configuration.get('league_webhook_id') and configuration.get('league_webhook_token'):
                fetcher.post_discord_webhook(
                    configuration.get_str('league_webhook_id'),
                    configuration.get_str('league_webhook_token'),
                    '{entry} reported {f.entry_games}-{f.opponent_games} vs {opponent}'.format(f=form, entry=entry_name, opponent=opp_name)
                )
            else:
                logger.warning('Not posting manual report to discord because not configured.')
        db().begin()
        match.insert_match(dtutil.now(), form.entry, form.entry_games, form.opponent, form.opponent_games, None, None, mtgo_match_id)
        db().commit()
        return True
    except LockNotAcquiredException:
        form.errors['entry'] = 'Cannot report right now, somebody else is reporting a match for you or your opponent. Try again a bit later'
        return False
    finally:
        db().release_lock('deck_id:{id}'.format(id=form.opponent))
        db().release_lock('deck_id:{id}'.format(id=form.entry))
Exemplo n.º 17
0
def run() -> None:
    run_elo = False
    # pylint: disable=consider-using-enumerate
    for i in range(0, len(USERNAME_COLUMNS)):
        # pylint: disable=consider-using-enumerate
        for j in range(i + 1, len(USERNAME_COLUMNS)):
            sql = """
                SELECT p1.id AS p1_id, p2.id AS p2_id, '{col1}' AS col1, '{col2}' AS col2
                FROM person AS p1
                LEFT JOIN person AS p2
                ON p1.{col1} = p2.{col2} AND p1.id <> p2.id
                WHERE p1.id IS NOT NULL AND p2.id IS NOT NULL
            """.format(col1=USERNAME_COLUMNS[i], col2=USERNAME_COLUMNS[j])
            pairs = [Container(row) for row in db().execute(sql)]
            if len(pairs) > 0:
                run_elo = True
            for pair in pairs:
                person.squash(pair.p1_id, pair.p2_id, pair.col1, pair.col2)
    if run_elo:
        logger.warning('Running maintenance task to correct all Elo ratings.')
        elo.run()
Exemplo n.º 18
0
def login(user: Optional[str] = None, password: Optional[str] = None) -> None:
    if user is None:
        user = configuration.get_str('to_username')
    if password is None:
        password = configuration.get_str('to_password')
    if user == '' or password == '':
        logger.warning('No TappedOut credentials provided')
        return
    url = 'https://tappedout.net/accounts/login/'
    session = fetcher_internal.SESSION
    response = session.get(url)

    match = re.search(
        r"<input type='hidden' name='csrfmiddlewaretoken' value='(\w+)' />",
        response.text)
    if match is None:
        # Already logged in?
        return
    csrf = match.group(1)

    data = {
        'csrfmiddlewaretoken': csrf,
        'next': '/',
        'username': user,
        'password': password,
    }
    headers = {
        'referer': url,
    }
    logger.warning('Logging in to TappedOut as {0}'.format(user))
    response = session.post(url, data=data, headers=headers)
    if response.status_code == 403:
        logger.warning('Failed to log in')
def run() -> None:
    api_key = configuration.get('poeditor_api_key')
    if api_key is None:
        logger.warning('Missing poeditor.com API key')
        return
    client = POEditorAPI(api_token=api_key)
    languages = client.list_project_languages('162959')
    # pull down translations
    for locale in languages:
        logger.warning('Found translation for {code}: {percent}%'.format(code=locale['code'], percent=locale['percentage']))
        if locale['percentage'] > 0:
            path = os.path.join('shared_web', 'translations', locale['code'].replace('-', '_'), 'LC_MESSAGES')
            if not os.path.exists(path):
                os.makedirs(path)
            pofile = os.path.join(path, 'messages.po')
            logger.warning('Saving to {0}'.format(pofile))
            if os.path.exists(pofile):
                os.remove(pofile)
            client.export(project_id='162959', language_code=locale['code'],
                          local_file=pofile, filters=['translated', 'not_fuzzy'])

    # Compile .po files into .mo files
    validate_translations.ad_hoc()
    compiler = compile_catalog()
    compiler.directory = os.path.join('shared_web', 'translations')
    compiler.domain = ['messages']
    compiler.run()
    # hack for English - We need an empty folder so that Enlish shows up in the 'Known Languages' list.
    path = os.path.join('shared_web', 'translations', 'en', 'LC_MESSAGES')
    if not os.path.exists(path):
        os.makedirs(path)
def scrape(ignore_competition_ids=None):
    if ignore_competition_ids is None:
        ignore_competition_ids = []
    where = "d.id NOT IN (SELECT deck_id FROM deck_match) AND d.source_id = (SELECT id FROM source WHERE name = 'Gatherling')"
    if ignore_competition_ids:
        where += ' AND d.competition_id NOT IN ({ids})'.format(
            ids=', '.join([str(id) for id in ignore_competition_ids]))
    decks = deck.load_decks(where, order_by='d.competition_id')
    if len(decks) == 0:
        logger.warning('No more competitions to insert matches for.')
        return
    ds, competition_id = [], decks[0].competition_id
    for d in decks:
        if d.competition_id != competition_id:
            # Arbitrary cutoff of number of decks to say "these are decks with no matches not a full unlogged competition".
            if len(ds) >= 4:
                break
            else:
                logger.warning(
                    'Skipping {id} because deck count is {n}.'.format(
                        id=competition_id, n=len(ds)))
                ds = []
                competition_id = d.competition_id
        ds.append(d)
    matches = []
    for d in ds:
        matches += gatherling.tournament_matches(d)
    if len(matches) == 0:
        logger.warning(
            'Found no matches in {id} so trying the next competition.'.format(
                id=competition_id))
        scrape(ignore_competition_ids + [competition_id])
    gatherling.add_ids(matches, ds)
    gatherling.insert_matches_without_dupes(ds[0].created_date, matches)
Exemplo n.º 21
0
def scrape(limit: int = 1) -> None:
    page = 1
    while page <= limit:
        time.sleep(0.1)
        url = 'https://www.mtggoldfish.com/deck/custom/penny_dreadful?page={n}#online'.format(
            n=page)
        soup = BeautifulSoup(
            fetcher.internal.fetch(url, character_encoding='utf-8'),
            'html.parser')
        raw_decks = soup.find_all('div', {'class': 'deck-tile'})
        if len(raw_decks) == 0:
            logger.warning(
                'No decks found in {url} so stopping.'.format(url=url))
            break
        for raw_deck in raw_decks:
            d = Container({'source': 'MTG Goldfish'})
            a = raw_deck.select_one('h2 > span.deck-price-online > a')
            d.identifier = re.findall(r'/deck/(\d+)#online', a.get('href'))[0]
            d.url = 'https://www.mtggoldfish.com/deck/{identifier}#online'.format(
                identifier=d.identifier)
            d.name = a.contents[0].strip()
            d.mtggoldfish_username = without_by(
                raw_deck.select_one(
                    'div.deck-tile-author').contents[0].strip())
            try:
                d.created_date = scrape_created_date(d)
            except InvalidDataException as e:
                msg = f'Got {e} trying to find a created_date in {d}, {raw_deck}'
                logger.error(msg)
                raise InvalidDataException(msg)
            time.sleep(1)
            d.cards = scrape_decklist(d)
            err = vivify_or_error(d)
            if err:
                logger.warning(err)
                continue
            deck.add_deck(d)
        page += 1
Exemplo n.º 22
0
def ad_hoc() -> None:
    login()
    logger.warning('Logged in to TappedOut: {is_authorised}'.format(is_authorised=is_authorised()))
    raw_decks = fetch_decks()
    for raw_deck in raw_decks:
        try:
            if is_authorised():
                details = fetch_deck_details(raw_deck)
                if details is None:
                    logger.warning(f'Failed to get details for {raw_deck}')
                else:
                    raw_deck.update(details) # type: ignore
            raw_deck = set_values(raw_deck)
            deck.add_deck(raw_deck)
        except InvalidDataException as e:
            logger.warning('Skipping {slug} because of {e}'.format(slug=raw_deck.get('slug', '-no slug-'), e=e))
Exemplo n.º 23
0
def run():
    start = time.time()
    multiverse.reindex()
    end = time.time()
    logger.warning('Indexing done in {t} seconds'.format(t=(end - start)))
Exemplo n.º 24
0
def stats() -> Response:
    val: Dict[str, Any] = {}
    try:
        last_switcheroo = calc_last_switcheroo()
        if last_switcheroo:
            val['last_switcheroo'] = dtutil.dt2ts(
                last_switcheroo.start_time_aware())
    except AttributeError as e:
        logger.warning(f'Unable to calculate last_switcheroo: {e}')

    val['formats'] = {}
    base_query = db.DB.session.query(match.Match.format_id, Format.name,
                                     func.count(match.Match.format_id)).join(
                                         match.Match.format).group_by(
                                             match.Match.format_id)
    for m in base_query.order_by(func.count(
            match.Match.format_id).desc()).all():
        (format_id, format_name, num_matches) = m
        val['formats'][format_name] = {}
        val['formats'][format_name]['name'] = format_name
        val['formats'][format_name]['num_matches'] = num_matches
    last_week = dtutil.now() - dtutil.ts2dt(7 * 24 * 60 * 60)
    for m in base_query.filter(match.Match.start_time > last_week).order_by(
            func.count(match.Match.format_id).desc()).all():
        (format_id, format_name, num_matches) = m
        val['formats'][format_name]['last_week'] = {}
        val['formats'][format_name]['last_week']['num_matches'] = num_matches
        stmt = text("""
            SELECT b.*
            FROM user AS b
            INNER JOIN (
                SELECT user.id
                FROM user
                LEFT JOIN match_players ON match_players.user_id = user.id
                LEFT JOIN `match` ON `match`.id = match_players.match_id
                WHERE `match`.format_id = :fid
                    AND `match`.start_time IS NOT NULL
                    AND `match`.start_time > DATE_SUB(NOW(), INTERVAL 7 DAY)
                GROUP BY user.id
            ) AS a ON a.id = b.id
        """)
        players = db.DB.session.query(
            db.User).from_statement(stmt).params(fid=format_id).all()
        val['formats'][format_name]['last_week']['recent_players'] = [
            p.name for p in players
        ]
    last_last_week = dtutil.now() - dtutil.ts2dt(2 * 7 * 24 * 60 * 60)
    for m in base_query.filter(match.Match.start_time < last_week).filter(
            match.Match.start_time > last_last_week).order_by(
                func.count(match.Match.format_id).desc()).all():
        (format_id, format_name, num_matches) = m
        val['formats'][format_name]['last_last_week'] = {}
        val['formats'][format_name]['last_last_week'][
            'num_matches'] = num_matches
        stmt = text("""
            SELECT b.*
            FROM user AS b
            INNER JOIN (
                SELECT user.id
                FROM user
                LEFT JOIN match_players ON match_players.user_id = user.id
                LEFT JOIN `match` ON `match`.id = match_players.match_id
                WHERE `match`.format_id = :fid
                    AND `match`.start_time IS NOT NULL
                    AND `match`.start_time > DATE_SUB(NOW(), INTERVAL 14 DAY)
                    AND `match`.start_time < DATE_SUB(NOW(), INTERVAL 7 DAY)
                GROUP BY user.id
            ) AS a ON a.id = b.id
        """)
        players = db.DB.session.query(
            db.User).from_statement(stmt).params(fid=format_id).all()
        val['formats'][format_name]['last_last_week']['recent_players'] = [
            p.name for p in players
        ]

    last_month = dtutil.now() - dtutil.ts2dt(30 * 24 * 60 * 60)
    for m in base_query.filter(match.Match.start_time > last_month).order_by(
            func.count(match.Match.format_id).desc()).all():
        (format_id, format_name, num_matches) = m
        val['formats'][format_name]['last_month'] = {}
        val['formats'][format_name]['last_month']['num_matches'] = num_matches
        stmt = text("""
            SELECT b.*
            FROM user AS b
            INNER JOIN (
                SELECT user.id
                FROM user
                LEFT JOIN match_players ON match_players.user_id = user.id
                LEFT JOIN `match` ON `match`.id = match_players.match_id
                WHERE `match`.format_id = :fid
                    AND `match`.start_time IS NOT NULL
                    AND `match`.start_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
                GROUP BY user.id
            ) AS a ON a.id = b.id
        """)
        players = db.DB.session.query(
            db.User).from_statement(stmt).params(fid=format_id).all()
        val['formats'][format_name]['last_month']['recent_players'] = [
            p.name for p in players
        ]
    return return_json(val)
Exemplo n.º 25
0
def report(form: ReportForm) -> bool:
    try:
        db().get_lock('deck_id:{id}'.format(id=form.entry))
        db().get_lock('deck_id:{id}'.format(id=form.opponent))

        pdbot = form.get('api_token',
                         None) == configuration.get('pdbot_api_token')

        entry_deck_id = int(form.entry)
        opponent_deck_id = int(form.opponent)

        ds = {
            d.id: d
            for d in deck.load_decks(
                f'd.id IN ({entry_deck_id}, {opponent_deck_id})')
        }
        entry_deck = ds.get(entry_deck_id)
        opponent_deck = ds.get(opponent_deck_id)

        if not entry_deck:
            form.errors[
                'entry'] = 'This deck does not appear to exist. Please try again.'
            return False

        if not opponent_deck:
            form.errors[
                'opponent'] = 'This deck does not appear to exist. Please try again.'
            return False

        if not pdbot:
            if entry_deck.retired:
                form.errors[
                    'entry'] = 'Your deck is retired, you cannot report results for it. If you need to do this, contact a mod on the Discord.'
                return False
            if opponent_deck.retired:
                form.errors[
                    'opponent'] = "Your opponent's deck is retired, you cannot report results against it. If you need to do this, please contact a mod on the Discord."
                return False

        for m in match.load_matches_by_deck(form):
            if int(form.opponent) == m.opponent_deck_id:
                form.errors[
                    'result'] = 'This match was reported as You {game_wins}–{game_losses} {opponent} {date}'.format(
                        game_wins=m.game_wins,
                        game_losses=m.game_losses,
                        opponent=m.opponent,
                        date=dtutil.display_date(m.date))
                return False

        counts = deck.count_matches(form.entry, form.opponent)
        if counts[int(form.entry)] >= 5:
            form.errors['entry'] = 'You already have 5 matches reported'
            return False
        if counts[int(form.opponent)] >= 5:
            form.errors[
                'opponent'] = 'Your opponent already has 5 matches reported'
            return False

        if pdbot:
            mtgo_match_id = form.get('matchID', None)
        else:
            mtgo_match_id = None
        match.insert_match(dtutil.now(), form.entry, form.entry_games,
                           form.opponent, form.opponent_games, None, None,
                           mtgo_match_id)
        if not pdbot:
            if configuration.get('league_webhook_id') and configuration.get(
                    'league_webhook_token'):
                fetch_tools.post_discord_webhook(
                    configuration.get_str('league_webhook_id'),
                    configuration.get_str('league_webhook_token'),
                    '{entry} reported {f.entry_games}-{f.opponent_games} vs {opponent}'
                    .format(f=form,
                            entry=entry_deck.person,
                            opponent=opponent_deck.person))
            else:
                logger.warning(
                    'Not posting manual report to discord because not configured.'
                )
        return True
    except LockNotAcquiredException:
        form.errors[
            'entry'] = 'Cannot report right now, somebody else is reporting a match for you or your opponent. Try again a bit later'
        return False
    finally:
        db().release_lock('deck_id:{id}'.format(id=form.opponent))
        db().release_lock('deck_id:{id}'.format(id=form.entry))