def post(self): self._require_registration() event_name = self.request.get("name", None) website = WebsiteHelper.format_url(self.request.get("website", None)) status, failures = SuggestionCreator.createOffseasonEventSuggestion( author_account_key=self.user_bundle.account.key, name=event_name, start_date=self.request.get("start_date", None), end_date=self.request.get("end_date", None), website=website, venue_name=self.request.get("venue_name", None), address=self.request.get("venue_address", None), city=self.request.get("venue_city", None), state=self.request.get("venue_state", None), country=self.request.get("venue_country", None) ) if status != 'success': # Don't completely wipe form data if validation fails self.template_values.update({ 'status': status, 'failures': failures, 'name': self.request.get('name', None), 'start_date': self.request.get('start_date', None), 'end_date': self.request.get('end_date', None), 'website': self.request.get('website', None), 'venue_address': self.request.get('venue_address', None), }) self.response.out.write( jinja2_engine.render('suggestions/suggest_offseason_event.html', self.template_values)) else: subject, body = self._gen_notification_email(event_name) OutgoingNotificationHelper.send_admin_alert_email(subject, body) self.redirect('/suggest/offseason?status=%s' % status)
def post(self): self._require_registration() event_key = self.request.get("event_key") status, suggestion = SuggestionCreator.createEventMediaSuggestion( author_account_key=self.user_bundle.account.key, media_url=self.request.get("media_url"), event_key=event_key) if status == 'success': # Send an update to the FUN slack slack_sitevar = Sitevar.get_or_insert('slack.hookurls') if slack_sitevar: slack_url = slack_sitevar.contents.get('fun', '') if slack_url: message_body = u"{0} ({1}) has suggested a video for <https://www.thebluealliance.com/event/{2}|{2}>: https://youtu.be/{3}.\nSee all suggestions at https://www.thebluealliance.com/suggest/event/media/review".format( self.user_bundle.account.display_name, self.user_bundle.account.email, event_key, suggestion.contents['foreign_key']).encode('utf-8') OutgoingNotificationHelper.send_slack_alert(slack_url, message_body, []) self.redirect('/suggest/event/media?event_key=%s&status=%s' % (event_key, status))
def create_target_model(self, suggestion): event_id = self.request.get("event_short", None) event_key = str(self.request.get("year")) + str.lower( str(self.request.get("event_short"))) if not event_id: # Need to supply a key :( return 'missing_key', None if not Event.validate_key_name(event_key): # Bad event key generated return 'bad_key', None start_date = None if self.request.get("start_date"): start_date = datetime.strptime(self.request.get("start_date"), "%Y-%m-%d") end_date = None if self.request.get("end_date"): end_date = datetime.strptime(self.request.get("end_date"), "%Y-%m-%d") existing_event = Event.get_by_id(event_key) if existing_event: return 'duplicate_key', None event = Event( id=event_key, end_date=end_date, event_short=self.request.get("event_short"), event_type_enum=EventType.OFFSEASON, district_key=None, venue=self.request.get("venue"), venue_address=self.request.get("venue_address"), city=self.request.get("city"), state_prov=self.request.get("state"), country=self.request.get("country"), name=self.request.get("name"), short_name=self.request.get("short_name"), start_date=start_date, website=self.request.get("website"), year=int(self.request.get("year")), official=False, ) EventManipulator.createOrUpdate(event) author = suggestion.author.get() OutgoingNotificationHelper.send_suggestion_result_email( to=author.email, subject="[TBA] Offseason Event Suggestion: {}".format(event.name), email_body="""Dear {}, Thank you for suggesting an offseason event to The Blue Alliance. Your suggestion has been approved and you can find the event at https://thebluealliance.com/event/{} If you are the event's organizer and would like to upload teams attending, match videos, or real-time match results to TBA before or during the event, you can do so using the TBA EventWizard - request auth keys here: https://www.thebluealliance.com/request/apiwrite Thanks for helping make TBA better, The Blue Alliance Admins """.format(author.nickname, event_key)) return 'success', event_key
def post(self): self._require_registration() event_name = self.request.get("name", None) status, failures = SuggestionCreator.createOffseasonEventSuggestion( author_account_key=self.user_bundle.account.key, name=event_name, start_date=self.request.get("start_date", None), end_date=self.request.get("end_date", None), website=self.request.get("website", None), venue_name=self.request.get("venue_name", None), address=self.request.get("venue_address", None), city=self.request.get("venue_city", None), state=self.request.get("venue_state", None), country=self.request.get("venue_country", None) ) if status != 'success': # Don't completely wipe form data if validation fails self.template_values.update({ 'status': status, 'failures': failures, 'name': self.request.get('name', None), 'start_date': self.request.get('start_date', None), 'end_date': self.request.get('end_date', None), 'website': self.request.get('website', None), 'venue_address': self.request.get('venue_address', None), }) self.response.out.write( jinja2_engine.render('suggestions/suggest_offseason_event.html', self.template_values)) else: subject, body = self._gen_notification_email(event_name) OutgoingNotificationHelper.send_admin_alert_email(subject, body) self.redirect('/suggest/offseason?status=%s' % status)
def create_target_model(self, suggestion): event_id = self.request.get("event_short", None) event_key = str(self.request.get("year")) + str.lower(str(self.request.get("event_short"))) if not event_id: # Need to supply a key :( return 'missing_key', None if not Event.validate_key_name(event_key): # Bad event key generated return 'bad_key', None start_date = None if self.request.get("start_date"): start_date = datetime.strptime(self.request.get("start_date"), "%Y-%m-%d") end_date = None if self.request.get("end_date"): end_date = datetime.strptime(self.request.get("end_date"), "%Y-%m-%d") existing_event = Event.get_by_id(event_key) if existing_event: return 'duplicate_key', None first_code = self.request.get("first_code", '') event = Event( id=event_key, end_date=end_date, event_short=self.request.get("event_short"), event_type_enum=EventType.OFFSEASON, district_key=None, venue=self.request.get("venue"), venue_address=self.request.get("venue_address"), city=self.request.get("city"), state_prov=self.request.get("state"), country=self.request.get("country"), name=self.request.get("name"), short_name=self.request.get("short_name"), start_date=start_date, website=self.request.get("website"), year=int(self.request.get("year")), first_code=first_code, official=(not first_code == ''), ) EventManipulator.createOrUpdate(event) author = suggestion.author.get() OutgoingNotificationHelper.send_suggestion_result_email( to=author.email, subject="[TBA] Offseason Event Suggestion: {}".format(event.name), email_body="""Dear {}, Thank you for suggesting an offseason event to The Blue Alliance. Your suggestion has been approved and you can find the event at https://thebluealliance.com/event/{} If you are the event's organizer and would like to upload teams attending, match videos, or real-time match results to TBA before or during the event, you can do so using the TBA EventWizard - request auth keys here: https://www.thebluealliance.com/request/apiwrite Thanks for helping make TBA better, The Blue Alliance Admins """.format(author.nickname, event_key) ) return 'success', event_key
def post(self): self._require_registration() event_key = self.request.get("event_key") status, suggestion = SuggestionCreator.createEventMediaSuggestion( author_account_key=self.user_bundle.account.key, media_url=self.request.get("media_url"), event_key=event_key) if status == 'success': # Send an update to the FUN slack slack_sitevar = Sitevar.get_or_insert('slack.hookurls') if slack_sitevar: slack_url = slack_sitevar.contents.get('fun', '') if slack_url: message_body = "{0} ({1}) has suggested a video for <https://thebluealliance.com/event/{2}|{2}>: https://youtu.be/{3}.\nSee all suggestions at https://www.thebluealliance.com/suggest/event/media/review".format( self.user_bundle.account.display_name, self.user_bundle.account.email, event_key, suggestion.contents['foreign_key']) OutgoingNotificationHelper.send_slack_alert( slack_url, message_body, []) self.redirect('/suggest/event/media?event_key=%s&status=%s' % (event_key, status))
def post(self): self.verify_permissions() suggestion_id = int(self.request.get("suggestion_id")) verdict = self.request.get("verdict") message = self.request.get("user_message") admin_email_body = None email_body = None user = None event_key = None status = '' if verdict == "accept": status = 'accept' auth_id, user, event_key, email_body = self._process_accepted(suggestion_id) admin_email_body = u"""{} ({}) has accepted the request with the following message: {} View the key: https://www.thebluealliance.com/admin/api_auth/edit/{} """.format(self.user_bundle.account.display_name, self.user_bundle.account.email, message, auth_id).encode('utf-8') elif verdict == "reject": suggestion = Suggestion.get_by_id(suggestion_id) event_key = suggestion.contents['event_key'] user = suggestion.author.get() event = Event.get_by_id(event_key) self._process_rejected(suggestion.key.id()) status = 'reject' email_body = u"""Hi {}, We have reviewed your request for auth tokens for {} {} and have regretfully declined to issue keys with the following message: {} If you have any questions, please don't hesitate to reach out to us at [email protected] Thanks, TBA Admins """.format(user.display_name, event.year, event.name, message).encode('utf-8') admin_email_body = u"""{} ({}) has rejected this request with the following reason: {} """.format(self.user_bundle.account.display_name, self.user_bundle.account.email, message).encode('utf-8') # Notify the user their keys are available if email_body: mail.send_mail(sender="The Blue Alliance Contact <*****@*****.**>", to=user.email, subject="The Blue Alliance Auth Tokens for {}".format(event_key), body=email_body) if admin_email_body: # Subject should match the one in suggest_apiwrite_controller subject = "Trusted API Key Request for {}".format(event_key) OutgoingNotificationHelper.send_admin_alert_email(subject, admin_email_body) self.redirect("/suggest/apiwrite/review?success={}".format(status))
def post(self): self.verify_permissions() suggestion_id = int(self.request.get("suggestion_id")) verdict = self.request.get("verdict") message = self.request.get("user_message") admin_email_body = None email_body = None user = None event_key = None status = '' if verdict == "accept": status = 'accept' auth_id, user, event_key, email_body = self._process_accepted(suggestion_id) admin_email_body = """{} ({}) has accepted the request with the following message: {} View the key: https://www.thebluealliance.com/admin/api_auth/edit/{} """.format(self.user_bundle.account.display_name, self.user_bundle.account.email, message, auth_id) elif verdict == "reject": suggestion = Suggestion.get_by_id(suggestion_id) event_key = suggestion.contents['event_key'] user = suggestion.author.get() event = Event.get_by_id(event_key) self._process_rejected(suggestion.key.id()) status = 'reject' email_body = """Hi {}, We have reviewer your request for auth tokens for {} {} and have regretfully declined with the following message: {} If you have any questions, please don't hesitate to reach out to us at [email protected] Thanks, TBA Admins """.format(user.display_name, event.year, event.name, message) admin_email_body = """{} ({}) has rejected this request with the following reason: {} """.format(self.user_bundle.account.display_name, self.user_bundle.account.email, message) # Notify the user their keys are available if email_body: mail.send_mail(sender="The Blue Alliance Contact <*****@*****.**>", to=user.email, subject="The Blue Alliance Auth Tokens for {}".format(event_key), body=email_body) if admin_email_body: # Subject should match the one in suggest_apiwrite_controller subject = "Trusted API Key Request for {}".format(event_key) OutgoingNotificationHelper.send_admin_alert_email(subject, admin_email_body) self.redirect("/suggest/apiwrite/review?success={}".format(status))
def post(self): self._require_login() auth_types = self.request.get_all("auth_types", []) clean_auth_types = filter(lambda a: int(a) in AuthType.write_type_names.keys(), auth_types) event_key = self.request.get("event_key", None) status = SuggestionCreator.createApiWriteSuggestion( author_account_key=self.user_bundle.account.key, event_key=event_key, affiliation=self.request.get("role", None), auth_types=clean_auth_types, ) if status == 'success': subject, body = self._gen_notification_email(event_key, self.user_bundle) OutgoingNotificationHelper.send_admin_alert_email(subject, body) self.redirect('/request/apiwrite?status={}'.format(status), abort=True)
def post(self): self._require_registration() team_key = self.request.get("team_key") year_str = self.request.get("year") status, suggestion = SuggestionCreator.createTeamMediaSuggestion( author_account_key=self.user_bundle.account.key, media_url=self.request.get("media_url"), team_key=team_key, year_str=year_str) if status == 'success' and suggestion.contents.get('media_type') == MediaType.GRABCAD: # Send an update to the frcdesigns slack slack_sitevar = Sitevar.get_or_insert('slack.hookurls') if slack_sitevar: slack_url = slack_sitevar.contents.get('tbablog', '') if slack_url: model_details = json.loads(suggestion.contents['details_json']) message_body = u"{0} ({1}) has suggested a CAD model for team <https://www.thebluealliance.com/team/{2}/{3}|{2} in {3}>.".format( self.user_bundle.account.display_name, self.user_bundle.account.email, team_key[3:], year_str).encode('utf-8') image_attachment = { "footer": "<https://www.thebluealliance.com/suggest/cad/review|See all suggestions> on The Blue Alliance", "fallback": "CAD model", "title": model_details['model_name'], "title_link": "https://grabcad.com/library/{}".format(suggestion.contents['foreign_key']), "image_url": model_details['model_image'].replace('card.jpg', 'large.png'), "fields": [ { "title": "Accept", "value": "<https://www.thebluealliance.com/suggest/cad/review?action=accept&id={}|Click Here>".format(suggestion.key.id()), "short": True, }, { "title": "Reject", "value": "<https://www.thebluealliance.com/suggest/cad/review?action=reject&id={}|Click Here>".format(suggestion.key.id()), "short": True, } ], } OutgoingNotificationHelper.send_slack_alert(slack_url, message_body, [image_attachment]) self.redirect('/suggest/team/media?team_key=%s&year=%s&status=%s' % (team_key, year_str, status))
def post(self): self._require_login() auth_types = self.request.get_all("auth_types", []) clean_auth_types = filter( lambda a: int(a) in AuthType.write_type_names.keys(), auth_types) event_key = self.request.get("event_key", None) status = SuggestionCreator.createApiWriteSuggestion( author_account_key=self.user_bundle.account.key, event_key=event_key, affiliation=self.request.get("role", None), auth_types=clean_auth_types, ) if status == 'success': subject, body = self._gen_notification_email( event_key, self.user_bundle) OutgoingNotificationHelper.send_admin_alert_email(subject, body) self.redirect('/request/apiwrite?status={}'.format(status), abort=True)
def _fastpath_review(self): self.verify_permissions() suggestion = Suggestion.get_by_id(self.request.get('id')) status = None if suggestion and suggestion.target_model == 'robot': if suggestion.review_state == Suggestion.REVIEW_PENDING: slack_message = None if self.request.get('action') == 'accept': self._process_accepted(suggestion.key.id()) status = 'accepted' slack_message = "{0} ({1}) accepted the <https://grabcad.com/library/{2}|suggestion> for team <https://thebluealliance.com/team/{3}/{4}|{3} in {4}>".format( self.user_bundle.account.display_name, self.user_bundle.account.email, suggestion.contents['foreign_key'], suggestion.contents['reference_key'][3:], suggestion.contents['year']) elif self.request.get('action') == 'reject': self._process_rejected(suggestion.key.id()) status = 'rejected' slack_message = "{0} ({1}) rejected the <https://grabcad.com/library/{2}|suggestion> for team <https://thebluealliance.com/team/{3}/{4}|{3} in {4}>".format( self.user_bundle.account.display_name, self.user_bundle.account.email, suggestion.contents['foreign_key'], suggestion.contents['reference_key'][3:], suggestion.contents['year']) if slack_message: slack_sitevar = Sitevar.get_or_insert('slack.hookurls') if slack_sitevar: slack_url = slack_sitevar.contents.get('tbablog', '') OutgoingNotificationHelper.send_slack_alert( slack_url, slack_message) else: status = 'already_reviewed' else: status = 'bad_suggestion' if status: self.redirect('/suggest/review?status={}'.format(status), abort=True)
def _fastpath_review(self): self.verify_permissions() suggestion = Suggestion.get_by_id(self.request.get('id')) status = None if suggestion and suggestion.target_model == 'robot': if suggestion.review_state == Suggestion.REVIEW_PENDING: slack_message = None if self.request.get('action') == 'accept': self._process_accepted(suggestion.key.id()) status = 'accepted' slack_message = "{0} ({1}) accepted the <https://grabcad.com/library/{2}|suggestion> for team <https://thebluealliance.com/team/{3}/{4}|{3} in {4}>".format( self.user_bundle.account.display_name, self.user_bundle.account.email, suggestion.contents['foreign_key'], suggestion.contents['reference_key'][3:], suggestion.contents['year'] ) elif self.request.get('action') == 'reject': self._process_rejected(suggestion.key.id()) status = 'rejected' slack_message = "{0} ({1}) rejected the <https://grabcad.com/library/{2}|suggestion> for team <https://thebluealliance.com/team/{3}/{4}|{3} in {4}>".format( self.user_bundle.account.display_name, self.user_bundle.account.email, suggestion.contents['foreign_key'], suggestion.contents['reference_key'][3:], suggestion.contents['year'] ) if slack_message: slack_sitevar = Sitevar.get_or_insert('slack.hookurls') if slack_sitevar: slack_url = slack_sitevar.contents.get('tbablog', '') OutgoingNotificationHelper.send_slack_alert(slack_url, slack_message) else: status = 'already_reviewed' else: status = 'bad_suggestion' if status: self.redirect('/suggest/review?status={}'.format(status), abort=True)
def get(self): hook_sitevars = Sitevar.get_by_id('slack.hookurls') if not hook_sitevars: return channel_url = hook_sitevars.contents.get('suggestion-nag') if not channel_url: return counts = map( lambda t: SuggestionFetcher.count(Suggestion.REVIEW_PENDING, t), Suggestion.MODELS) nag_text = "There are pending suggestions!\n" suggestions_to_nag = False for count, name in zip(counts, Suggestion.MODELS): if count > 0: suggestions_to_nag = True nag_text += "*{0}*: {1} pending suggestions\n".format( Suggestion.MODEL_NAMES.get(name), count) if suggestions_to_nag: nag_text += "_Review them on <https://www.thebluealliance.com/suggest/review|TBA>_" OutgoingNotificationHelper.send_slack_alert(channel_url, nag_text)
def get(self): hook_sitevars = Sitevar.get_by_id('slack.hookurls') if not hook_sitevars: return channel_url = hook_sitevars.contents.get('suggestion-nag') if not channel_url: return counts = map(lambda t: SuggestionFetcher.count(Suggestion.REVIEW_PENDING, t), Suggestion.MODELS) nag_text = "There are pending suggestions!\n" suggestions_to_nag = False for count, name in zip(counts, Suggestion.MODELS): if count > 0: suggestions_to_nag = True nag_text += "*{0}*: {1} pending suggestions\n".format( Suggestion.MODEL_NAMES.get(name), count ) if suggestions_to_nag: nag_text += "_Review them on <https://www.thebluealliance.com/suggest/review|TBA>_" OutgoingNotificationHelper.send_slack_alert(channel_url, nag_text)
def update_bluezone(cls, live_events): """ Find the current best match to watch Currently favors showing something over nothing, is okay with switching TO a feed in the middle of a match, but avoids switching FROM a feed in the middle of a match. 1. Get the earliest predicted unplayed match across all live events 2. Get all matches that start within TIME_BUCKET of that match 3. Switch to hottest match in that bucket unless MAX_TIME_PER_MATCH is hit (in which case blacklist for the future) 4. Repeat """ now = datetime.datetime.now() logging.info("[BLUEZONE] Current time: {}".format(now)) to_log = '--------------------------------------------------\n' to_log += "[BLUEZONE] Current time: {}\n".format(now) slack_sitevar = Sitevar.get_or_insert('slack.hookurls') slack_url = None if slack_sitevar: slack_url = slack_sitevar.contents.get('bluezone', '') bluezone_config = Sitevar.get_or_insert('bluezone') logging.info("[BLUEZONE] Config (updated {}): {}".format( bluezone_config.updated, bluezone_config.contents)) to_log += "[BLUEZONE] Config (updated {}): {}\n".format( bluezone_config.updated, bluezone_config.contents) current_match_key = bluezone_config.contents.get('current_match') last_match_key = bluezone_config.contents.get('last_match') current_match_predicted_time = bluezone_config.contents.get( 'current_match_predicted') if current_match_predicted_time: current_match_predicted_time = datetime.datetime.strptime( current_match_predicted_time, cls.TIME_PATTERN) current_match_switch_time = bluezone_config.contents.get( 'current_match_switch_time') if current_match_switch_time: current_match_switch_time = datetime.datetime.strptime( current_match_switch_time, cls.TIME_PATTERN) else: current_match_switch_time = now blacklisted_match_keys = bluezone_config.contents.get( 'blacklisted_matches', set()) if blacklisted_match_keys: blacklisted_match_keys = set(blacklisted_match_keys) blacklisted_event_keys = bluezone_config.contents.get( 'blacklisted_events', set()) if blacklisted_event_keys: blacklisted_event_keys = set(blacklisted_event_keys) current_match = Match.get_by_id( current_match_key) if current_match_key else None last_match = Match.get_by_id( last_match_key) if last_match_key else None logging.info("[BLUEZONE] live_events: {}".format( [le.key.id() for le in live_events])) to_log += "[BLUEZONE] live_events: {}\n".format( [le.key.id() for le in live_events]) live_events = filter(lambda e: e.webcast_status != 'offline', live_events) for event in live_events: # Fetch all matches and details asynchronously event.prep_matches() event.prep_details() logging.info("[BLUEZONE] Online live_events: {}".format( [le.key.id() for le in live_events])) to_log += "[BLUEZONE] Online live_events: {}\n".format( [le.key.id() for le in live_events]) upcoming_matches = cls.get_upcoming_matches(live_events) upcoming_matches = filter(lambda m: m.predicted_time is not None, upcoming_matches) upcoming_predictions = cls.get_upcoming_match_predictions(live_events) # (1, 2) Find earliest predicted unplayed match and all other matches # that start within TIME_BUCKET of that match upcoming_matches.sort(key=lambda match: match.predicted_time) potential_matches = [] time_cutoff = None logging.info( "[BLUEZONE] all upcoming matches sorted by predicted time: {}". format([um.key.id() for um in upcoming_matches])) to_log += "[BLUEZONE] all upcoming sorted by predicted time: {}\n".format( [um.key.id() for um in upcoming_matches]) for match in upcoming_matches: if match.predicted_time: if time_cutoff is None: time_cutoff = match.predicted_time + cls.TIME_BUCKET potential_matches.append(match) elif match.predicted_time < time_cutoff: potential_matches.append(match) else: break # Matches are sorted by predicted_time logging.info( "[BLUEZONE] potential_matches sorted by predicted time: {}".format( [pm.key.id() for pm in potential_matches])) to_log += "[BLUEZONE] potential_matches sorted by predicted time: {}\n".format( [pm.key.id() for pm in potential_matches]) # (3) Choose hottest match that's not blacklisted cls.calculate_match_hotness(potential_matches, upcoming_predictions) potential_matches.sort(key=lambda match: -match.hotness) logging.info( "[BLUEZONE] potential_matches sorted by hotness: {}".format( [pm.key.id() for pm in potential_matches])) to_log += "[BLUEZONE] potential_matches sorted by hotness: {}\n".format( [pm.key.id() for pm in potential_matches]) bluezone_matches = [] new_blacklisted_match_keys = set() # If the current match hasn't finished yet, don't even bother cutoff_time = current_match_switch_time + cls.MAX_TIME_PER_MATCH logging.info( "[BLUEZONE] Current match played? {}, now = {}, cutoff = {}". format(current_match.has_been_played if current_match else None, now, cutoff_time)) to_log += "[BLUEZONE] Current match played? {}, now = {}, cutoff = {}\n".format( current_match.has_been_played if current_match else None, now, cutoff_time) if current_match and not current_match.has_been_played and now < cutoff_time \ and current_match_key not in blacklisted_match_keys \ and current_match.event_key_name not in blacklisted_event_keys: logging.info("[BLUEZONE] Keeping current match {}".format( current_match.key.id())) to_log += "[BLUEZONE] Keeping current match {}\n".format( current_match.key.id()) bluezone_matches.append(current_match) for match in potential_matches: if len(bluezone_matches) >= 2: # one current, one future break logging.info("[BLUEZONE] Trying potential match: {}".format( match.key.id())) to_log += "[BLUEZONE] Trying potential match: {}\n".format( match.key.id()) if filter(lambda m: m.key.id() == match.key.id(), bluezone_matches): logging.info("[BLUEZONE] Match {} already chosen".format( match.key.id())) to_log += "[BLUEZONE] Match {} already chosen\n".format( match.key.id()) continue if match.event_key_name in blacklisted_event_keys: logging.info( "[BLUEZONE] Event {} is blacklisted, skipping...".format( match.event_key_name)) to_log += "[BLUEZONE] Event {} is blacklisted, skipping...\n".format( match.event_key_name) continue if match.key.id() not in blacklisted_match_keys: if match.key.id() == current_match_key: if current_match_predicted_time and cutoff_time < now and len( potential_matches) > 1: # We've been on this match too long new_blacklisted_match_keys.add(match.key.id()) logging.info( "[BLUEZONE] Adding match to blacklist: {}".format( match.key.id())) to_log += "[BLUEZONE] Adding match to blacklist: {}\n".format( match.key.id()) logging.info( "[BLUEZONE] scheduled time: {}, now: {}".format( current_match_predicted_time, now)) to_log += "[BLUEZONE] scheduled time: {}, now: {}\n".format( current_match_predicted_time, now) OutgoingNotificationHelper.send_slack_alert( slack_url, "Blacklisting match {}. Predicted time: {}, now: {}" .format(match.key.id(), current_match_predicted_time, now)) else: # We can continue to use this match bluezone_matches.append(match) logging.info( "[BLUEZONE] Continuing to use match: {}".format( match.key.id())) to_log += "[BLUEZONE] Continuing to use match: {}\n".format( match.key.id()) else: # Found a new good match bluezone_matches.append(match) logging.info( "[BLUEZONE] Found a good new match: {}".format( match.key.id())) to_log += "[BLUEZONE] Found a good new match: {}\n".format( match.key.id()) else: logging.info("[BLUEZONE] Match already blacklisted: {}".format( match.key.id())) to_log += "[BLUEZONE] Match already blacklisted: {}\n".format( match.key.id()) new_blacklisted_match_keys.add(match.key.id()) if not bluezone_matches: logging.info("[BLUEZONE] No match selected") to_log += "[BLUEZONE] No match selected\n" logging.info("[BLUEZONE] All selected matches: {}".format( [m.key.id() for m in bluezone_matches])) to_log += "[BLUEZONE] All selected matches: {}\n".format( [m.key.id() for m in bluezone_matches]) # (3) Switch to hottest match fake_event = cls.build_fake_event() if bluezone_matches: bluezone_match = bluezone_matches[0] real_event = filter( lambda x: x.key_name == bluezone_match.event_key_name, live_events)[0] # Create Fake event for return fake_event.webcast_json = json.dumps( [real_event.current_webcasts[0]]) if bluezone_match.key_name != current_match_key: current_match_switch_time = now logging.info("[BLUEZONE] Switching to: {}".format( bluezone_match.key.id())) to_log += "[BLUEZONE] Switching to: {}\n".format( bluezone_match.key.id()) OutgoingNotificationHelper.send_slack_alert( slack_url, "It is now {}. Switching BlueZone to {}, scheduled for {} and predicted to be at {}." .format(now, bluezone_match.key.id(), bluezone_match.time, bluezone_match.predicted_time)) if not current_match or current_match.has_been_played: last_match = current_match # Only need to update if things changed if bluezone_match.key_name != current_match_key or new_blacklisted_match_keys != blacklisted_match_keys: FirebasePusher.update_event(fake_event) bluezone_config.contents = { 'current_match': bluezone_match.key.id(), 'last_match': last_match.key.id() if last_match else '', 'current_match_predicted': bluezone_match.predicted_time.strftime(cls.TIME_PATTERN), 'blacklisted_matches': list(new_blacklisted_match_keys), 'blacklisted_events': list(blacklisted_event_keys), 'current_match_switch_time': current_match_switch_time.strftime(cls.TIME_PATTERN), } bluezone_config.put() # Log to cloudstorage log_dir = '/tbatv-prod-hrd.appspot.com/tba-logging/bluezone/' log_file = 'bluezone_{}.txt'.format(now.date()) full_path = log_dir + log_file existing_contents = '' if full_path in set( [f.filename for f in cloudstorage.listbucket(log_dir)]): with cloudstorage.open(full_path, 'r') as existing_file: existing_contents = existing_file.read() with cloudstorage.open(full_path, 'w') as new_file: new_file.write(existing_contents + to_log) bluezone_matches.insert(0, last_match) bluezone_matches = filter(lambda m: m is not None, bluezone_matches) FirebasePusher.replace_event_matches('bluezone', bluezone_matches) return fake_event
def post(self): self._require_registration() team_key = self.request.get("team_key") year_str = self.request.get("year") status, suggestion = SuggestionCreator.createTeamMediaSuggestion( author_account_key=self.user_bundle.account.key, media_url=self.request.get("media_url"), team_key=team_key, year_str=year_str) if status == 'success' and suggestion.contents.get( 'media_type') == MediaType.GRABCAD: # Send an update to the frcdesigns slack slack_sitevar = Sitevar.get_or_insert('slack.hookurls') if slack_sitevar: slack_url = slack_sitevar.contents.get('tbablog', '') if slack_url: model_details = json.loads( suggestion.contents['details_json']) message_body = "{0} ({1}) has suggested a CAD model for team <https://www.thebluealliance.com/team/{2}/{3}|{2} in {3}>.".format( self.user_bundle.account.display_name, self.user_bundle.account.email, team_key[3:], year_str) image_attachment = { "footer": "<https://www.thebluealliance.com/suggest/cad/review|See all suggestions> on The Blue Alliance", "fallback": "CAD model", "title": model_details['model_name'], "title_link": "https://grabcad.com/library/{}".format( suggestion.contents['foreign_key']), "image_url": model_details['model_image'].replace( 'card.jpg', 'large.png'), "fields": [{ "title": "Accept", "value": "<https://www.thebluealliance.com/suggest/cad/review?action=accept&id={}|Click Here>" .format(suggestion.key.id()), "short": True, }, { "title": "Reject", "value": "<https://www.thebluealliance.com/suggest/cad/review?action=reject&id={}|Click Here>" .format(suggestion.key.id()), "short": True, }], } OutgoingNotificationHelper.send_slack_alert( slack_url, message_body, [image_attachment]) self.redirect('/suggest/team/media?team_key=%s&year=%s&status=%s' % (team_key, year_str, status))
def update_bluezone(cls, live_events): """ Find the current best match to watch Currently favors showing something over nothing, is okay with switching TO a feed in the middle of a match, but avoids switching FROM a feed in the middle of a match. 1. Get the earliest predicted unplayed match across all live events 2. Get all matches that start within TIME_BUCKET of that match 3. Switch to hottest match in that bucket unless MAX_TIME_PER_MATCH is hit (in which case blacklist for the future) 4. Repeat """ now = datetime.datetime.now() logging.info("[BLUEZONE] Current time: {}".format(now)) to_log = '--------------------------------------------------\n' to_log += "[BLUEZONE] Current time: {}\n".format(now) slack_sitevar = Sitevar.get_or_insert('slack.hookurls') slack_url = None if slack_sitevar: slack_url = slack_sitevar.contents.get('bluezone', '') bluezone_config = Sitevar.get_or_insert('bluezone') logging.info("[BLUEZONE] Config (updated {}): {}".format(bluezone_config.updated, bluezone_config.contents)) to_log += "[BLUEZONE] Config (updated {}): {}\n".format(bluezone_config.updated, bluezone_config.contents) current_match_key = bluezone_config.contents.get('current_match') last_match_key = bluezone_config.contents.get('last_match') current_match_predicted_time = bluezone_config.contents.get('current_match_predicted') if current_match_predicted_time: current_match_predicted_time = datetime.datetime.strptime(current_match_predicted_time, cls.TIME_PATTERN) current_match_switch_time = bluezone_config.contents.get('current_match_switch_time') if current_match_switch_time: current_match_switch_time = datetime.datetime.strptime(current_match_switch_time, cls.TIME_PATTERN) else: current_match_switch_time = now blacklisted_match_keys = bluezone_config.contents.get('blacklisted_matches', set()) if blacklisted_match_keys: blacklisted_match_keys = set(blacklisted_match_keys) blacklisted_event_keys = bluezone_config.contents.get('blacklisted_events', set()) if blacklisted_event_keys: blacklisted_event_keys = set(blacklisted_event_keys) current_match = Match.get_by_id(current_match_key) if current_match_key else None last_match = Match.get_by_id(last_match_key) if last_match_key else None logging.info("[BLUEZONE] live_events: {}".format([le.key.id() for le in live_events])) to_log += "[BLUEZONE] live_events: {}\n".format([le.key.id() for le in live_events]) live_events = filter(lambda e: e.webcast_status != 'offline', live_events) for event in live_events: # Fetch all matches and details asynchronously event.prep_matches() event.prep_details() logging.info("[BLUEZONE] Online live_events: {}".format([le.key.id() for le in live_events])) to_log += "[BLUEZONE] Online live_events: {}\n".format([le.key.id() for le in live_events]) upcoming_matches = cls.get_upcoming_matches(live_events) upcoming_matches = filter(lambda m: m.predicted_time is not None, upcoming_matches) upcoming_predictions = cls.get_upcoming_match_predictions(live_events) # (1, 2) Find earliest predicted unplayed match and all other matches # that start within TIME_BUCKET of that match upcoming_matches.sort(key=lambda match: match.predicted_time) potential_matches = [] time_cutoff = None logging.info("[BLUEZONE] all upcoming matches sorted by predicted time: {}".format([um.key.id() for um in upcoming_matches])) to_log += "[BLUEZONE] all upcoming sorted by predicted time: {}\n".format([um.key.id() for um in upcoming_matches]) for match in upcoming_matches: if match.predicted_time: if time_cutoff is None: time_cutoff = match.predicted_time + cls.TIME_BUCKET potential_matches.append(match) elif match.predicted_time < time_cutoff: potential_matches.append(match) else: break # Matches are sorted by predicted_time logging.info("[BLUEZONE] potential_matches sorted by predicted time: {}".format([pm.key.id() for pm in potential_matches])) to_log += "[BLUEZONE] potential_matches sorted by predicted time: {}\n".format([pm.key.id() for pm in potential_matches]) # (3) Choose hottest match that's not blacklisted cls.calculate_match_hotness(potential_matches, upcoming_predictions) potential_matches.sort(key=lambda match: -match.hotness) logging.info("[BLUEZONE] potential_matches sorted by hotness: {}".format([pm.key.id() for pm in potential_matches])) to_log += "[BLUEZONE] potential_matches sorted by hotness: {}\n".format([pm.key.id() for pm in potential_matches]) bluezone_matches = [] new_blacklisted_match_keys = set() # If the current match hasn't finished yet, don't even bother cutoff_time = current_match_switch_time + cls.MAX_TIME_PER_MATCH logging.info("[BLUEZONE] Current match played? {}, now = {}, cutoff = {}".format(current_match.has_been_played if current_match else None, now, cutoff_time)) to_log += "[BLUEZONE] Current match played? {}, now = {}, cutoff = {}\n".format(current_match.has_been_played if current_match else None, now, cutoff_time) if current_match and not current_match.has_been_played and now < cutoff_time \ and current_match_key not in blacklisted_match_keys \ and current_match.event_key_name not in blacklisted_event_keys: logging.info("[BLUEZONE] Keeping current match {}".format(current_match.key.id())) to_log += "[BLUEZONE] Keeping current match {}\n".format(current_match.key.id()) bluezone_matches.append(current_match) for match in potential_matches: if len(bluezone_matches) >= 2: # one current, one future break logging.info("[BLUEZONE] Trying potential match: {}".format(match.key.id())) to_log += "[BLUEZONE] Trying potential match: {}\n".format(match.key.id()) if filter(lambda m: m.key.id() == match.key.id(), bluezone_matches): logging.info("[BLUEZONE] Match {} already chosen".format(match.key.id())) to_log += "[BLUEZONE] Match {} already chosen\n".format(match.key.id()) continue if match.event_key_name in blacklisted_event_keys: logging.info("[BLUEZONE] Event {} is blacklisted, skipping...".format(match.event_key_name)) to_log += "[BLUEZONE] Event {} is blacklisted, skipping...\n".format(match.event_key_name) continue if match.key.id() not in blacklisted_match_keys: if match.key.id() == current_match_key: if current_match_predicted_time and cutoff_time < now and len(potential_matches) > 1: # We've been on this match too long new_blacklisted_match_keys.add(match.key.id()) logging.info("[BLUEZONE] Adding match to blacklist: {}".format(match.key.id())) to_log += "[BLUEZONE] Adding match to blacklist: {}\n".format(match.key.id()) logging.info("[BLUEZONE] scheduled time: {}, now: {}".format(current_match_predicted_time, now)) to_log += "[BLUEZONE] scheduled time: {}, now: {}\n".format(current_match_predicted_time, now) OutgoingNotificationHelper.send_slack_alert(slack_url, "Blacklisting match {}. Predicted time: {}, now: {}".format(match.key.id(), current_match_predicted_time, now)) else: # We can continue to use this match bluezone_matches.append(match) logging.info("[BLUEZONE] Continuing to use match: {}".format(match.key.id())) to_log += "[BLUEZONE] Continuing to use match: {}\n".format(match.key.id()) else: # Found a new good match bluezone_matches.append(match) logging.info("[BLUEZONE] Found a good new match: {}".format(match.key.id())) to_log += "[BLUEZONE] Found a good new match: {}\n".format(match.key.id()) else: logging.info("[BLUEZONE] Match already blacklisted: {}".format(match.key.id())) to_log += "[BLUEZONE] Match already blacklisted: {}\n".format(match.key.id()) new_blacklisted_match_keys.add(match.key.id()) if not bluezone_matches: logging.info("[BLUEZONE] No match selected") to_log += "[BLUEZONE] No match selected\n" logging.info("[BLUEZONE] All selected matches: {}".format([m.key.id() for m in bluezone_matches])) to_log += "[BLUEZONE] All selected matches: {}\n".format([m.key.id() for m in bluezone_matches]) # (3) Switch to hottest match fake_event = cls.build_fake_event() if bluezone_matches: bluezone_match = bluezone_matches[0] real_event = filter(lambda x: x.key_name == bluezone_match.event_key_name, live_events)[0] # Create Fake event for return fake_event.webcast_json = json.dumps([real_event.current_webcasts[0]]) if bluezone_match.key_name != current_match_key: current_match_switch_time = now logging.info("[BLUEZONE] Switching to: {}".format(bluezone_match.key.id())) to_log += "[BLUEZONE] Switching to: {}\n".format(bluezone_match.key.id()) OutgoingNotificationHelper.send_slack_alert(slack_url, "It is now {}. Switching BlueZone to {}, scheduled for {} and predicted to be at {}.".format(now, bluezone_match.key.id(), bluezone_match.time, bluezone_match.predicted_time)) if not current_match or current_match.has_been_played: last_match = current_match # Only need to update if things changed if bluezone_match.key_name != current_match_key or new_blacklisted_match_keys != blacklisted_match_keys: FirebasePusher.update_event(fake_event) bluezone_config.contents = { 'current_match': bluezone_match.key.id(), 'last_match': last_match.key.id() if last_match else '', 'current_match_predicted': bluezone_match.predicted_time.strftime(cls.TIME_PATTERN), 'blacklisted_matches': list(new_blacklisted_match_keys), 'blacklisted_events': list(blacklisted_event_keys), 'current_match_switch_time': current_match_switch_time.strftime(cls.TIME_PATTERN), } bluezone_config.put() # Log to cloudstorage log_dir = '/tbatv-prod-hrd.appspot.com/tba-logging/' log_file = 'bluezone_{}.txt'.format(now.date()) full_path = log_dir + log_file existing_contents = '' if full_path in set([f.filename for f in cloudstorage.listbucket(log_dir)]): with cloudstorage.open(full_path, 'r') as existing_file: existing_contents = existing_file.read() with cloudstorage.open(full_path, 'w') as new_file: new_file.write(existing_contents + to_log) bluezone_matches.insert(0, last_match) bluezone_matches = filter(lambda m: m is not None, bluezone_matches) FirebasePusher.replace_event_matches('bluezone', bluezone_matches) return fake_event