def get_bets(league: str, season: int, matchday: int): """ Displays all matches for a matchday with entries for betting :param league: The league to display :param season: The season to display :param matchday: The matchday to display :return: The response """ validated = validate_matchday(league, season, matchday) if validated is None: return abort(404) league, season, matchday = validated matches: List[Match] = Match.query.filter_by( matchday=matchday, season=season, league=league).options(db.joinedload(Match.home_team), db.joinedload(Match.away_team)).all() if len(matches) == 0: flash("Den angegebenen Spieltag gibt es nicht", "danger") return redirect(url_for("bets.get_bets")) matches.sort(key=lambda x: x.kickoff) has_started = matches[0].has_started all_started = matches[-1].has_started bets = Bet.query.filter_by(matchday=matchday, season=season, league=league, user_id=current_user.id).all() matchday_points = 0 for bet in bets: if bet.points is not None: matchday_points += bet.points bet_match_map = {(x.home_team_abbreviation, x.away_team_abbreviation): x for x in bets} bet_infos = [] for match_item in matches: index = (match_item.home_team_abbreviation, match_item.away_team_abbreviation) bet_infos.append((match_item, bet_match_map.get(index))) leaderboard = None if has_started: leaderboard = Leaderboard( league, season, matchday, DisplayBotsSettings.get_state(current_user)) return render_template("betting/bets.html", matchday=matchday, season=season, league=league, bet_infos=bet_infos, matchday_points=matchday_points, has_started=has_started, all_started=all_started, leaderboard=leaderboard)
def from_db(cls, user: User, list_name: str, service: ListService, media_type: MediaType, media_subtype: Optional[MediaSubType], minimum_diff: int, include_complete: bool): """ Generates UpdateWrapper objects based on a couple of parameters and the current database contents :param user: The user for whom to load the updates :param list_name: The list name for which to load the updates :param service: The service for which to load the updates :param media_type: The media type for which to load the updates :param media_subtype: If specified, limits the results to a specific media subtype (example: Light novels) :param minimum_diff: Specifies a minimum diff value :param include_complete: Specifies whether completed items should be included :return: A list of UpdateWrapper objects """ media_lists: List[MediaList] = MediaList.query \ .filter_by( user=user, name=list_name, service=service, media_type=media_type ) \ .options( db.joinedload(MediaList.media_list_items) .subqueryload(MediaListItem.media_user_state) .subqueryload(MediaUserState.media_id) .subqueryload(MediaId.chapter_guess) ) \ .options( db.joinedload(MediaList.media_list_items) .subqueryload(MediaListItem.media_user_state) .subqueryload(MediaUserState.media_id) .subqueryload(MediaId.media_item) .subqueryload(MediaItem.media_ids) ) \ .options( db.joinedload(MediaList.media_list_items) .subqueryload(MediaListItem.media_user_state) .subqueryload(MediaUserState.media_id) .subqueryload(MediaId.media_item) .subqueryload(MediaItem.ln_releases) ) \ .all() return cls.from_media_lists(media_lists, media_subtype, minimum_diff, include_complete)
def media(media_item_id: int): """ Displays information on a media item :param media_item_id: The ID of the media item :return: None """ media_item: MediaItem = MediaItem.query\ .options(db.joinedload(MediaItem.media_ids) .subqueryload(MediaId.media_user_states) ).filter_by(id=media_item_id).first() if media_item is None: abort(404) if current_user is not None: media_user_states = [] for media_id in media_item.media_ids: user_states = [ x for x in media_id.media_user_states if x.user_id == current_user.id ] media_user_states += user_states media_user_states.sort(key=lambda x: x.media_id.service.value) else: media_user_states = [] return render_template( "media/media.html", media_item=media_item, media_user_states=media_user_states )
def load_history(cls, league: str, season: int, matchday: int) -> \ List[Tuple[User, List["LeaderboardEntry"]]]: """ Loads the history for the previous matches in a season for each user :param league: The league for which to retrieve the history :param season: The season for which to retrieve the history :param matchday: The matchday for which to retrieve the history :return: The history as a list of tuples of users and a list of the corresponding LeaderboardEntry objects. Sorted by current position """ entries: List[LeaderboardEntry] = [ x for x in LeaderboardEntry.query.filter_by( league=league, season=season).options( db.joinedload(LeaderboardEntry.user)).all() if x.matchday <= matchday ] entries.sort(key=lambda x: x.matchday) history_dict: Dict[int, List[LeaderboardEntry]] = {} for entry in entries: if entry.user_id not in history_dict: history_dict[entry.user_id] = [] history_dict[entry.user_id].append(entry) history_list = [(history[-1].user, history) for _, history in history_dict.items()] history_list.sort(key=lambda x: x[1][-1].position) return history_list
def match(league: str, season: int, matchday: int, matchup: str): """ Displays a single match :param league: The league of the match :param season: The season of the match :param matchday: The matchday of the match :param matchup: The matchup string ('hometeam_awayteam') :return: The Response """ try: home, away = matchup.split("_") match_item = Match.query.filter_by( league=league, season=season, matchday=matchday, home_team_abbreviation=home, away_team_abbreviation=away ).options(db.joinedload(Match.goals)).first() if match_item is None: raise ValueError() except ValueError: return abort(404) bets: List[Bet] = Bet.query.filter_by( league=league, season=season, matchday=matchday, home_team_abbreviation=home, away_team_abbreviation=away ).options(db.joinedload(Bet.user) .subqueryload(User.profile) .subqueryload(UserProfile.favourite_team)).all() if not DisplayBotsSettings.get_state(current_user): bets = [ x for x in bets if DisplayBotsSettings.bot_symbol() not in x.user.username ] bets.sort(key=lambda x: x.user_id) if match_item.has_started: bets.sort(key=lambda x: x.points, reverse=True) return render_template( "info/match.html", match=match_item, bets=bets )
def update_bet_points(): """ Updates the bet points :return: None """ bets = Bet.query.options(db.joinedload(Bet.match)).all() for bet in bets: bet.points = bet.evaluate() db.session.commit()
def send_new_update_notifications(): """ Sends out telegram notifications for media updates :return: None """ start = time.time() app.logger.info("Starting check for notifications") user_states: List[MediaUserState] = MediaUserState.query\ .options( db.joinedload(MediaUserState.media_id) .subqueryload(MediaId.media_item) .subqueryload(MediaItem.media_ids) ) \ .options( db.joinedload(MediaUserState.media_id) .subqueryload(MediaId.chapter_guess) ) \ .options( db.joinedload(MediaUserState.user) .subqueryload(User.telegram_chat_id) ) \ .options(db.joinedload(MediaUserState.media_notification)) \ .all() notification_settings: Dict[int, NotificationSetting] = { x.user_id: x for x in NotificationSetting.query.all() } for user_state in user_states: settings = notification_settings.get(user_state.user_id) if settings is None or not settings.value: continue else: handle_notification(user_state, settings) db.session.commit() app.logger.info(f"Completed check for notifications in " f"{time.time() - start}s.")
def update_manga_chapter_guesses(): """ Updates the manga chapter guesses :return: None """ start = time.time() app.logger.info("Starting update of manga chapter guesses") anilist_ids: List[MediaId] = MediaId.query\ .filter_by( media_type=MediaType.MANGA, media_subtype=MediaSubType.MANGA, service=ListService.ANILIST ) \ .options(db.joinedload(MediaId.media_user_states)) \ .options(db.joinedload(MediaId.media_item)) \ .options(db.joinedload(MediaId.chapter_guess)) \ .all() for anilist_id in anilist_ids: if len(anilist_id.media_user_states) == 0: # We only update chapter guesses for items that are actively used # by users, since each update requires an HTTP request to anilist. # We don't want to be rate limited. continue elif anilist_id.chapter_guess is None: new_guess = MangaChapterGuess(media_id=anilist_id) db.session.add(new_guess) db.session.commit() guess = new_guess else: guess = anilist_id.chapter_guess app.logger.debug(f"Updating chapter guess for {anilist_id.service_id}") guess.update_guess() db.session.commit() db.session.commit() app.logger.info(f"Finished updating manga chapter guesses " f"in {time.time() - start}")
def team(team_abbreviation: int): """ Displays information about a single team :param team_abbreviation: The ID of the team to display :return: The response """ team_info = Team.query.filter_by( abbreviation=team_abbreviation ).options(db.joinedload(Team.players) .subqueryload(Player.goals)).first() if team_info is None: return abort(404) recent_matches = [x for x in team_info.matches if x.finished] recent_matches.sort(key=lambda x: x.kickoff) recent_matches = recent_matches[-7:] match_data = [] for match_item in recent_matches: if match_item.home_team_abbreviation == team_abbreviation: opponent = match_item.away_team own_score = match_item.home_current_score opponent_score = match_item.away_current_score else: opponent = match_item.home_team own_score = match_item.away_current_score opponent_score = match_item.home_current_score if own_score > opponent_score: result = "win" elif own_score < opponent_score: result = "loss" else: result = "draw" score = "{}:{}".format(own_score, opponent_score) match_data.append(( match_item, opponent, score, result )) goal_data = [] for player in team_info.players: goals = [x for x in player.goals if not x.own_goal] goal_data.append((player.name, len(goals))) goal_data.sort(key=lambda x: x[1], reverse=True) return render_template( "info/team.html", team=team_info, goals=goal_data, matches=match_data )
def __init__(self, league: str, season: int, matchday: int, include_bots: bool): """ Initializes the leaerboard object :param league: The league of the leaderboard :param season: The season of the leaderboard :param matchday: The matchday of the leaderboard :param include_bots: Whether or not to include bots """ self.league = league self.season = season self.matchday = matchday self.include_bots = include_bots self.season_winners: Dict[int, List[str]] = {} for season_winner in SeasonWinner.query.filter_by(league=league).all(): user_id = season_winner.user_id winner_season = season_winner.season_string if user_id not in self.season_winners: self.season_winners[user_id] = [] self.season_winners[user_id].append(winner_season) self.matchday_winners: Dict[int, List[int]] = {} for matchday_winner in MatchdayWinner.query.filter_by( league=league, season=season).all(): user_id = matchday_winner.user_id winner_matchday = matchday_winner.matchday if user_id not in self.matchday_winners: self.matchday_winners[user_id] = [] self.matchday_winners[user_id].append(winner_matchday) self.ranking: List[LeaderboardEntry] = \ LeaderboardEntry.query.filter_by( league=league, season=season, matchday=matchday ).options(db.joinedload(LeaderboardEntry.user) .subqueryload(User.profile) .subqueryload(UserProfile.favourite_team)).all() self.ranking.sort(key=lambda x: x.position) self.history: List[Tuple[User, List["LeaderboardEntry"]]] \ = LeaderboardEntry.load_history(league, season, matchday) if not include_bots: self.ranking = [ x for x in self.ranking if DisplayBotsSettings.bot_symbol() not in x.user.username ] self.history = [ x for x in self.history if DisplayBotsSettings.bot_symbol() not in x[0].username ]
def __init__( self, league: str, season: int, matchday: int, user: Optional[User] ): """ Initializes the lague table object :param league: The league :param season: The season :param matchday: The matchday :param user: Optionally, a user. This will replace the real results with the bets of the player """ self.league = league self.season = season self.matchday = matchday self.user = user self.teams = Team.get_teams_for_season(league, season) self.matches = Match.query.filter_by( league=league, season=season ).filter(Match.matchday <= matchday).options( db.joinedload(Match.home_team), db.joinedload(Match.away_team) ).all() self.bets = [] if self.user is not None: self.bets = [ x for x in Bet.query.filter_by( user_id=self.user.id, league=league, season=season ).options(db.joinedload(Bet.match)).all() if x.match.matchday <= matchday ]
def anime_week(): """ Shows the seasonal anime schedule for a user's media entries :return: None """ media_user_states: List[MediaUserState] = MediaUserState.query\ .options( db.joinedload(MediaUserState.media_id) .subqueryload(MediaId.media_item) )\ .filter_by(user_id=current_user.id)\ .all() time_limit = datetime.fromtimestamp(time.time() + 7 * 24 * 60 * 60) media_user_states = [ x for x in media_user_states if x.media_id.media_item.next_episode_airing_time is not None and time_limit > x.media_id.media_item.next_episode_datetime ] weekdays = { 1: "Monday", 2: "Tuesday", 3: "Wednesday", 4: "Thursday", 5: "Friday", 6: "Saturday", 7: "Sunday" } entries_by_weekday = [] for weekday, weekday_name in weekdays.items(): entries = [] for user_state in media_user_states: next_airing = \ user_state.media_id.media_item.next_episode_datetime if next_airing.isoweekday() == weekday: entries.append(user_state) entries.sort( key=lambda x: x.media_id.media_item.next_episode_airing_time ) entries_by_weekday.append((weekday_name, entries)) return render_template( "schedule/anime_week.html", schedule=entries_by_weekday )
def create_categorized_matches() -> Dict[ Tuple[str, int], Dict[int, List[Match]] ]: """ Sorts matches into seasons and matchdars :return: The matches categorized like this: {(league, season): {matchday: [match, ...]}} """ seasons: Dict[Tuple[str, int], Dict[int, List[Match]]] = {} for match in Match.query.options( db.joinedload(Match.bets).subqueryload(Bet.user) ).all(): league_season = (match.league, match.season) matchday = match.matchday if league_season not in seasons: seasons[league_season] = {} if matchday not in seasons[league_season]: seasons[league_season][matchday] = [] seasons[league_season][matchday].append(match) return seasons
def get_due_matches(self) -> List[Match]: """ Checks if the reminder is due and returns a list of matches that the user still needs to bet on. :return: The matches for which the reminder is due """ now = datetime.utcnow() start = max(now, self.last_reminder_datetime) start_str = start.strftime("%Y-%m-%d:%H-%M-%S") then = now + self.reminder_time_delta then_str = then.strftime("%Y-%m-%d:%H-%M-%S") due_matches: List[Match] = [ x for x in Match.query.filter_by( season=Config.season(), league=Config.OPENLIGADB_LEAGUE ).all() if start_str < x.kickoff < then_str ] user_bet_matches = [ (bet.match.home_team_abbreviation, bet.match.away_team_abbreviation, bet.match.season) for bet in Bet.query.filter_by( user_id=self.user_id, season=Config.season(), league=Config.OPENLIGADB_LEAGUE ).options(db.joinedload(Bet.match)).all() ] to_remind = [] for match in due_matches: identifier = (match.home_team_abbreviation, match.away_team_abbreviation, match.season) if identifier not in user_bet_matches: to_remind.append(match) return to_remind
def do_media_item_inserts(): """ Preforms media item insertions :return: None """ # Prepare existing DB content media_items: Dict[Tuple, MediaItem] = { x.identifier_tuple: x for x in MediaItem.query.all() } media_ids: List[MediaId] = MediaId.query\ .options(db.joinedload(MediaId.media_item)) \ .options(db.joinedload(MediaId.media_user_states)) \ .all() media_service_items: \ Dict[ListService, Dict[MediaType, Dict[str, MediaId]]] = \ {x: {y: {} for y in MediaType} for x in ListService} for media_id in media_ids: service = media_id.service media_type = media_id.media_item.media_type service_id = media_id.service_id media_service_items[service][media_type][service_id] = media_id media_lists: Dict[Tuple, MediaList] = { x.identifier_tuple: x for x in MediaList.query.options(db.joinedload(MediaList.media_list_items)) .all() } # Add media list items, ids and user states while len(media_item_queue) > 0: queued = media_item_queue.pop(0) params, service, service_ids, user_state_params, list_params = queued service_id = service_ids.get(service) if service_id is None: app.logger.error(f"Missing service ID for service {service}") media_type = params["media_type"] app.logger.debug(f"Inserting {params['romaji_title']} into database") if service_id in media_service_items[service]: media_id = media_service_items[service][media_type][service_id] media_item = media_id.media_item media_item.update(MediaItem(**params)) else: generated = MediaItem(**params) identifier = generated.identifier_tuple existing = media_items.get(identifier) if existing is None: media_item = generated db.session.add(media_item) media_items[identifier] = media_item else: media_item = existing media_item.update(generated) # Insert Media IDs for extra_service, extra_id in service_ids.items(): associated = media_service_items[extra_service][media_type].get( extra_id ) if associated is None: media_id = MediaId( media_item=media_item, service=extra_service, service_id=extra_id ) db.session.add(media_id) db.session.commit() media_service_items[extra_service][media_type][extra_id] \ = media_id elif associated.media_item.id != media_item.id: associated.media_item_id = media_item.id # Insert User States if user_state_params is not None: media_id = media_service_items[service][media_type][service_id] user_state_params["media_id_id"] = media_id.id generated = MediaUserState(**user_state_params) existing_user_states = { x.user_id: x for x in media_id.media_user_states } user_id = user_state_params["user_id"] existing_user_state = existing_user_states.get(user_id) if existing_user_state is None: db.session.add(generated) user_state = generated db.session.commit() else: existing_user_state.update(generated) user_state = existing_user_state if list_params is not None: generated = MediaList(**list_params) identifier = generated.identifier_tuple existing_list = media_lists.get(identifier) if existing_list is None: db.session.add(generated) db.session.commit() media_list = generated media_lists[identifier] = media_list else: existing_list.update(generated) media_list = existing_list media_list_item_ids = [ x.media_user_state_id for x in media_list.media_list_items ] if user_state.id not in media_list_item_ids: list_item = MediaListItem( media_list_id=media_list.id, media_user_state_id=user_state.id ) db.session.add(list_item)