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)
예제 #14
0
    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)
예제 #15
0
    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)
예제 #16
0
    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
예제 #17
0
    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