示例#1
0
    def decorated_func(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except HTTPException as e:
            return jsonify({
                'status': e.code,
                'message': HTTP_STATUS_CODES[e.code]
            }), e.code
        except DebuggableException as e:
            app = get_app()
            app.bot.notify_error(e)

            e.print_info(app.logger)

            return jsonify({
                'status': 500,
                'message': HTTP_STATUS_CODES[500]
            }), 500
        except Exception as e:
            # noinspection PyBroadException
            try:
                get_app().bot.notify_error(e)
            except Exception:
                pass

            traceback.print_tb(e.__traceback__)
            print(e, flush=True, file=sys.stderr)

            return jsonify({
                'status': 500,
                'message': HTTP_STATUS_CODES[500]
            }), 500
def put_subscribe():
    class PostData(TypedDict):
        old_endpoint: str
        endpoint: str
        keys: Dict[str, str]

    if not current_user.is_role('admin'):
        return api_utils.response_unauthorized()

    post_data: PostData = request.get_json()
    old_endpoint = post_data['old_endpoint']
    endpoint = post_data['endpoint']
    keys = post_data['keys']

    app = get_app()
    user: WebUser = app.user_manager.get_user(
        UserId(old_endpoint, web_constants.PROVIDER_ID))

    # FIXME: Change internal ID of user and keys
    # FIXME: Change admin subscriptions as well? Need to verify this

    # FIXME: This code is not really done, but until we can send out daily menus in a consistent manner it'll
    #        have to be this way

    return api_utils.response_bad_request()
示例#3
0
    def get_administrators(self) -> 'List[User]':
        identifier = self.get_identifier()

        return [
            self.get_user(user) for user in get_app().admin_ids
            if user.provider == identifier
        ]
示例#4
0
def get_login_google():
    app = get_app()

    if google_client is None:
        init_google_client(app)

    if google_client is False:
        return redirect('/login/not_available')

    google_provider_cfg = get_google_provider_cfg()

    authorization_endpoint = google_provider_cfg['authorization_endpoint']

    state = {}

    if 'next' in request.args:
        next_url = request.args.get('next')
        parsed_next_url = urlparse(next_url)

        # Prevent changing the scheme or host
        if parsed_next_url.scheme != '' or parsed_next_url.netloc != '':
            return abort(400)

        state['next'] = parsed_next_url.geturl()

    request_uri = google_client.prepare_request_uri(
        authorization_endpoint,
        redirect_uri=url_for('.get_login_google_callback', _external=True),
        scope=['openid', 'email', 'profile'],
        state=quote(json.dumps(state))
    )
    return redirect(request_uri)
示例#5
0
def handle_facebook_webhook():
    try:
        app = get_app()
        data = request.get_json()

        if data and data['object'] == 'page':
            for entry in data['entry']:
                entry: dict

                if 'messaging' not in entry:
                    continue

                for event in entry['messaging']:
                    sender = event["sender"]["id"]
                    # recipient = event["recipient"]["id"]

                    user_manager = app.user_manager
                    user: FacebookUser = user_manager.get_user(UserId(sender, fb_constants.PROVIDER_ID), event=event)

                    if not isinstance(user, FacebookUser):
                        # FIXME: Rather have a check that when the user supports "read" markers, we mark as read
                        raise RuntimeError('Expected Facebook User')

                    app.task_executor.submit(_do_handle_facebook_webhook, event, user, app._get_current_object())

            return 'ok', 200

        print(pprint.pformat(data, indent=2), flush=True)

        return abort(400)
    except DebuggableException as e:
        app = get_app()
        app.bot.notify_error(e)

        e.print_info(app.logger)
    except Exception as e:
        try:
            get_app().bot.notify_error(e)
        except Exception:
            pass

        traceback.print_tb(e.__traceback__)
        print(e, flush=True, file=sys.stderr)

        return 'ok', 200
示例#6
0
    def get_locale(self) -> 'Optional[str]':
        stored_value = super().get_locale()

        if not stored_value:
            return get_app(
            ).bot_interfaces['facebook']['api_interface'].lookup_locale(
                self._id)

        return stored_value
示例#7
0
 def mark_message_seen(self):
     return get_app(
     ).bot_interfaces['facebook']['api_interface'].post_send_api({
         'recipient': {
             'id': self._id
         },
         'sender_action':
         'mark_seen'
     })
示例#8
0
    def initialise(self):
        import komidabot.facebook.postbacks as postbacks

        app = get_app()
        if app.config.get('TESTING') or app.config.get('DISABLED'):
            return

        data = postbacks.generate_postback_data(True,
                                                app.config.get('PRODUCTION'))
        app.bot_interfaces['facebook']['api_interface'].post_profile_api(data)
示例#9
0
def handle_facebook_verification():
    if request.args.get("hub.mode") == "subscribe" and request.args.get("hub.challenge"):
        if request.args.get('hub.verify_token', '') == get_app().config['VERIFY_TOKEN']:
            print("Verified")
            return escape(request.args.get('hub.challenge', ''))
        else:
            print("Wrong token")
            return "Error, wrong validation token"
    else:
        return abort(401)
示例#10
0
    def send_message(
            self, message: 'messages.Message') -> 'messages.MessageSendResult':
        result = self.get_message_handler().send_message(self, message)

        app = get_app()
        if app.config.get('VERBOSE'):
            print('Sending message to user {} got result {}'.format(
                self.id, result),
                  flush=True)

        return result
示例#11
0
        def menu_update(context, bot: 'Komidabot'):
            with context():
                if get_app().config.get('DISABLED'):
                    return

                try:
                    today = datetime.datetime.today().date()
                    week_start = today + datetime.timedelta(days=-today.weekday())

                    dates = [week_start + datetime.timedelta(days=i) for i in range(today.weekday(), 5)]
                    if today.weekday() >= 3:
                        dates += [week_start + datetime.timedelta(days=7 + i) for i in range(5)]

                    update_menus(dates=dates)
                except DebuggableException as e:
                    bot.notify_error(e)

                    e.print_info(get_app().logger)
                except Exception as e:
                    bot.notify_error(e)

                    get_app().logger.exception(e)
示例#12
0
    def decorated_func(*args, **kwargs):
        if get_app().config['TESTING']:
            # Skip validating signature if we're testing
            return func(*args, **kwargs)

        advertised = request.headers.get("X-Hub-Signature")
        if advertised is None:
            return False

        advertised = advertised.replace("sha1=", "", 1)
        data = request.get_data()

        received = hmac.new(
            key=get_app().config['APP_SECRET'].encode('raw_unicode_escape'),
            msg=data,
            digestmod=hashlib.sha1
        ).hexdigest()

        if hmac.compare_digest(advertised, received):
            return func(*args, **kwargs)

        return abort(401)
    def post_pass_thread_control(self, data: dict):
        response = self.session.post(BASE_ENDPOINT + API_VERSION + PASS_THREAD_CONTROL_API, params=self.base_parameters,
                                     headers=self.headers_post, data=json.dumps(data))

        app = get_app()

        if app.config.get('VERBOSE'):
            print('Received {} for request {}'.format(response.status_code, response.request.body), flush=True)
            print(response.content, flush=True)

        # response.raise_for_status()

        # return True

        return response.status_code == 200
    def post_send_api(self, data: dict) -> messages.MessageSendResult:
        response = self.session.post(BASE_ENDPOINT + API_VERSION + SEND_API, params=self.base_parameters,
                                     headers=self.headers_post, data=json.dumps(data))
        data = json.loads(response.content)

        app = get_app()

        if app.config.get('VERBOSE'):
            print('Received {} for request {}'.format(response.status_code, response.request.body), flush=True)
            print(response.content, flush=True)

        if response.status_code == 200:
            return messages.MessageSendResult.SUCCESS

        if 500 <= response.status_code < 600:
            return messages.MessageSendResult.EXTERNAL_ERROR

        if response.status_code == 400:
            code = data['error']['code']
            subcode = data['error']['error_subcode']

            # https://developers.facebook.com/docs/messenger-platform/reference/send-api/error-codes
            if code == 1200:
                # Temporary send message failure. Please try again later.
                return messages.MessageSendResult.EXTERNAL_ERROR
            if code == 100:
                if subcode == 2018001:
                    # No matching user found
                    return messages.MessageSendResult.GONE
            if code == 10:
                if subcode == 2018065:
                    # This message is sent outside of allowed window.
                    return messages.MessageSendResult.UNREACHABLE
                if subcode == 2018108:
                    # This Person Cannot Receive Messages: This person isn't receiving messages from you right now.
                    return messages.MessageSendResult.UNREACHABLE
                if subcode == 2018278:
                    # TODO: Get official description from FB once available
                    # Sent after March 4th to indicate the subscription message was denied
                    return messages.MessageSendResult.UNREACHABLE
            if code == 551:
                if subcode == 1545041:
                    # This person isn't available right now.
                    return messages.MessageSendResult.UNREACHABLE

        return messages.MessageSendResult.ERROR  # TODO: Further specify
示例#15
0
    def _send_text_message(
            user_id: users.UserId,
            message: messages.TextMessage) -> messages.MessageSendResult:
        data = {
            'recipient': {
                'id': user_id.id
            },
            'message': {
                'text': message.text
            },
            'messaging_type':
            TYPE_REPLY
            if triggers.SenderAspect in message.trigger else TYPE_SUBSCRIPTION,
        }

        return get_app(
        ).bot_interfaces['facebook']['api_interface'].post_send_api(data)
示例#16
0
def dispatch_daily_menus(trigger: triggers.SubscriptionTrigger):
    from komidabot.subscriptions.daily_menu import CHANNEL_ID as DAILY_MENU_ID

    # limiter = Limiter(20)  # Limit to 20 messages per second

    date = trigger.date or datetime.datetime.now().date()
    day = Day(date.isoweekday())

    app = get_app()

    verbose = app.config.get('VERBOSE')

    if verbose:
        print('Sending out subscription for {} ({})'.format(date, day.name), flush=True)

    message = messages.SubscriptionMenuMessage(trigger, date, app.translator)
    app.subscription_manager.deliver_message(DAILY_MENU_ID, message)
示例#17
0
    def _send_notification(subscription_information,
                           data) -> messages.MessageSendResult:
        app = get_app()

        try:
            response = webpush(
                subscription_info=subscription_information,
                data=json.dumps(data),
                vapid_private_key=app.config['VAPID_PRIVATE_KEY'],
                vapid_claims=copy.deepcopy(VAPID_CLAIMS))

            if app.config.get('VERBOSE'):
                print('Received {} for push {}'.format(
                    response.status_code,
                    subscription_information['endpoint']),
                      flush=True)
                print(response.content, flush=True)

            return messages.MessageSendResult.SUCCESS
        except WebPushException as e:
            response = e.response

            if app.config.get('VERBOSE'):
                print('Received {} for push {}'.format(
                    response.status_code,
                    subscription_information['endpoint']),
                      flush=True)
                print(response.content, flush=True)

            if 500 <= response.status_code < 600:
                return messages.MessageSendResult.EXTERNAL_ERROR

            if response.status_code == 429:  # Too many requests, rate limited
                pass  # TODO: Handle rate-limiting
            if response.status_code == 400:  # Invalid request
                return messages.MessageSendResult.ERROR
            if response.status_code == 404:  # Subscription not found
                return messages.MessageSendResult.GONE
            if response.status_code == 410:  # Subscription has been removed
                return messages.MessageSendResult.GONE
            if response.status_code == 413:  # Payload too large
                return messages.MessageSendResult.ERROR

            return messages.MessageSendResult.ERROR
示例#18
0
    def _send_template_message(
            user_id: users.UserId,
            message: 'TemplateMessage') -> messages.MessageSendResult:
        data = {
            'recipient': {
                'id': user_id.id
            },
            'message': {
                'attachment': {
                    'type': 'template',
                    'payload': message.payload
                }
            },
            'messaging_type':
            TYPE_REPLY
            if triggers.SenderAspect in message.trigger else TYPE_SUBSCRIPTION,
        }

        return get_app(
        ).bot_interfaces['facebook']['api_interface'].post_send_api(data)
示例#19
0
class Channel(subscriptions.SubscriptionChannel):
    def get_subscribed_users(self,
                             /,
                             query: Union[Query, Dict] = None) -> 'List[User]':
        if not isinstance(query, Query):
            query = self.get_query_from(query)

        assert isinstance(query, Query), 'query must be SubscriptionQuery'

        if query.campus is not None:
            raise NotImplementedError(
                'Cannot query by (day, campus) right now')

        app = get_app()
        user_manager = app.user_manager

        users = models.AppUser.find_subscribed_users_by_day(query.day)

        return [
            user for user in (user_manager.get_user(user) for user in users)
            if self.user_supported(user)
        ]
示例#20
0
    def _send_menu_message(
            user: users.User,
            message: messages.MenuMessage) -> messages.MessageSendResult:
        text = komidabot.menu.get_menu_text(message.menu, message.translator,
                                            user.get_locale())

        if text is None:
            return messages.MessageSendResult.ERROR

        data = {
            'recipient': {
                'id': user.get_internal_id()
            },
            'message': {
                'text': text
            },
            'messaging_type':
            TYPE_REPLY
            if triggers.SenderAspect in message.trigger else TYPE_SUBSCRIPTION,
        }

        return get_app(
        ).bot_interfaces['facebook']['api_interface'].post_send_api(data)
示例#21
0
def get_login_google_callback():
    app = get_app()

    if google_client is None:
        init_google_client(app)

    if google_client is False:
        return redirect('/login/not_available')

    code = request.args.get('code')
    state = json.loads(unquote(request.args.get('state')))
    next_url = state.get('next', '/')

    google_provider_cfg = get_google_provider_cfg()

    token_endpoint = google_provider_cfg['token_endpoint']
    token_url, headers, body = google_client.prepare_token_request(
        token_endpoint,
        authorization_response=request.url,
        redirect_url=request.base_url,
        code=code
    )
    token_response = requests.post(
        token_url,
        headers=headers,
        data=body,
        auth=(app.config.get('AUTH_GOOGLE_CLIENT_ID'), app.config.get('AUTH_GOOGLE_CLIENT_SECRET')),
    )

    try:
        google_client.parse_request_body_response(json.dumps(token_response.json()))
    except InvalidGrantError:
        # Invalid grant, let's try the login flow again
        if next_url != '/':
            return redirect(next_url)
        return redirect('/login/internal_error')
    except OAuth2Error:
        return redirect('/login/internal_error')

    userinfo_endpoint = google_provider_cfg['userinfo_endpoint']
    uri, headers, body = google_client.add_token(userinfo_endpoint)
    userinfo_response = requests.get(uri, headers=headers, data=body)

    # You want to make sure their email is verified.
    # The user authenticated with Google, authorized your
    # app, and now you've verified their email through Google!
    if userinfo_response.json().get('email_verified'):
        unique_id = userinfo_response.json()['sub']
        users_email = userinfo_response.json()['email']
        picture = userinfo_response.json()['picture']
        users_name = userinfo_response.json()['given_name']
    else:
        return redirect('/login/not_verified')

    user = RegisteredUser.find_by_provider_id('google', unique_id)
    if not user:
        if app_config.is_registrations_enabled():
            user = RegisteredUser.create('google', unique_id, users_name, users_email, picture)
            db.session.commit()
        else:
            return redirect('/login/login_closed')

    if not user.is_active:
        return redirect('/login/not_active')

    login_user(user)

    return redirect(next_url)
示例#22
0
        def daily_menu(context, bot: 'Komidabot'):
            with context():
                if get_app().config.get('DISABLED'):
                    return

                bot.trigger_received(triggers.SubscriptionTrigger())
示例#23
0
 def is_admin(self):
     user_id = self.id
     return user_id in get_app().admin_ids
示例#24
0
    def trigger_received(self, trigger: triggers.Trigger):
        with self.lock:  # TODO: Maybe only lock on critical sections?
            app = get_app()
            verbose = app.config.get('VERBOSE')

            if verbose:
                print('Komidabot received a trigger: {}'.format(type(trigger).__name__), flush=True)
                print(repr(trigger), flush=True)

            if isinstance(trigger, triggers.SubscriptionTrigger):
                dispatch_daily_menus(trigger)
                return

            if triggers.AtAdminAspect in trigger:
                return  # Don't process messages targeted at the admin

            locale = None
            message_handled = False

            # XXX: Disabled once more because responses aren't reliably in the language the user expects it to be
            # if triggers.LocaleAspect in trigger and trigger[triggers.LocaleAspect].confidence > 0.9:
            #     locale = trigger[triggers.LocaleAspect].locale

            if triggers.SenderAspect in trigger:
                sender = trigger[triggers.SenderAspect].sender
                campuses = Campus.get_all()

                # This ensures that when a user is marked as reachable in case they were unreachable at some point
                # TODO: We no longer mark users as reachable, need to think over the proper course of action
                # if sender.mark_reachable():
                #     db.session.commit()

                if locale is None:
                    locale = sender.get_locale()

                if triggers.NewUserAspect in trigger:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_NEW_USER(locale)))
                    msg = localisation.REPLY_INSTRUCTIONS(locale).format(
                        campuses=', '.join([campus.short_name.lower() for campus in campuses if campus.active])
                    )
                    sender.send_message(messages.TextMessage(trigger, msg))
                    sender.set_is_notified_new_site(True)
                    db.session.commit()

                    message_handled = True

                # TODO: Is this really how we want to handle input?
                #       Maybe we can add an IntentAspect, where the intent is the desired action the bot should take
                #       next? Ex. intents: admin message, get help, get menu, set preference (language, subscriptions)
                if isinstance(trigger, triggers.TextTrigger):
                    text = trigger.text
                    split = text.lower().split(' ')

                    if sender.is_admin():
                        if split[0] == 'setup':
                            if app.config.get('PRODUCTION'):
                                sender.send_message(messages.TextMessage(trigger, 'Not running setup on production'))
                                return
                            recreate_db()
                            create_standard_values()
                            import_dump(app.config['DUMP_FILE'])
                            sender.send_message(messages.TextMessage(trigger, 'Setup done'))
                            return
                        elif split[0] == 'update':
                            sender.send_message(messages.TextMessage(trigger, 'Updating menus...'))
                            update_menus(*split[1:])
                            sender.send_message(messages.TextMessage(trigger, 'Done updating menus...'))
                            return
                        elif split[0] == 'psid':  # TODO: Deprecated?
                            sender.send_message(messages.TextMessage(trigger, 'Your ID is {}'.format(sender.id.id)))
                            return

                    # TODO: Allow users to send more manual commands
                    #       See also the note prefacing the containing block
                    if not message_handled and split[0] == 'help':
                        msg = localisation.REPLY_INSTRUCTIONS(locale).format(
                            campuses=', '.join([campus.short_name.lower() for campus in campuses if campus.active])
                        )
                        sender.send_message(messages.TextMessage(trigger, msg))
                        return

                if app.config.get('COVID19_DISABLED'):
                    sender.send_message(messages.TextMessage(trigger, localisation.COVID19_UNAVAILABLE(locale)))
                    return

                requested_dates = []
                default_date = False

                if triggers.DatetimeAspect in trigger:
                    date_times = trigger[triggers.DatetimeAspect]
                    # TODO: Date parsing needs improving
                    requested_dates, invalid_date = nlp_dates.extract_days(date_times)

                    if invalid_date:
                        sender.send_message(messages.TextMessage(trigger, localisation.REPLY_INVALID_DATE(locale)))
                        return

                if len(requested_dates) > 1:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_TOO_MANY_DAYS(locale)))
                    return
                elif len(requested_dates) == 1:
                    date = requested_dates[0]
                else:
                    default_date = True
                    date = datetime.datetime.now().date()

                # TODO: How about getting the menu for the next day after a certain time of day?
                #       Only if we're returning the default day

                day = Day(date.isoweekday())

                if day == Day.SATURDAY or day == Day.SUNDAY:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_WEEKEND(locale)))
                    return

                requested_campuses = []
                default_campus = False

                if isinstance(trigger, triggers.TextTrigger):
                    text = trigger.text.lower()
                    for campus in campuses:
                        if not campus.active:
                            continue

                        for kw in campus.get_keywords():
                            if text.count(kw) > 0:
                                requested_campuses.append(campus)
                                break  # Prevent the same campus from being added multiple times

                if len(requested_campuses) > 1:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_TOO_MANY_CAMPUSES(locale)))
                    return
                elif len(requested_campuses) == 1:
                    campus = requested_campuses[0]
                else:
                    default_campus = True
                    campus = sender.get_campus_for_day(date)

                    if campus is None:  # User has no campus for the specified day
                        campus = Campus.get_by_short_name('cmi')

                if not campus.active:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_CAMPUS_INACTIVE(locale)
                                                             .format(campus=campus.name)))
                    return

                if message_handled and default_campus and default_date:
                    if isinstance(trigger, triggers.TextTrigger):
                        for word in ['menu', 'lunch', 'eten']:
                            if word in trigger.text:
                                break
                        else:
                            return
                    else:
                        return

                # if default_date and default_campus:
                #     if isinstance(trigger, triggers.TextTrigger):
                #         sender.send_message(messages.TextMessage(trigger,
                # localisation.REPLY_NO_DATE_OR_CAMPUS(locale)))
                #         msg = localisation.REPLY_INSTRUCTIONS(locale).format(
                #             campuses=', '.join([campus.short_name for campus in campuses])
                #         )
                #         sender.send_message(messages.TextMessage(trigger, msg))
                #         return
                #
                #     # User did not send a text message, so we'll continue anyway

                if not default_campus:
                    sender.set_campus_for_day(campus, date)
                    db.session.commit()

                if sender.get_is_notified_new_site() is False and sender.is_feature_active('new_site_notifications'):
                    if sender.send_message(messages.TextMessage(trigger, localisation.MESSAGE_NEW_SITE(locale))) \
                            == messages.MessageSendResult.SUCCESS:
                        sender.set_is_notified_new_site(True)
                        db.session.commit()

                closed = ClosingDays.find_is_closed(campus, date)

                if closed:
                    translation = closed.translatable.get_translation(locale, app.translator)

                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_CAMPUS_CLOSED(locale)
                                                             .format(campus=campus.name, date=str(date),
                                                                     reason=translation.translation)))
                    return

                # menu = komidabot.menu.prepare_menu_text(campus, date, app.translator, locale)
                menu = Menu.get_menu(campus, date)

                if menu is None:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_NO_MENU(locale)
                                                             .format(campus=campus.name, date=str(date))))
                else:
                    # sender.send_message(messages.TextMessage(trigger, menu))
                    sender.send_message(messages.MenuMessage(trigger, menu, app.translator))
示例#25
0
def _do_handle_facebook_webhook(event, user: FacebookUser, app):
    time.sleep(0.1)  # Yield

    with app.app_context():
        trigger = triggers.Trigger(aspects=[triggers.SenderAspect(user)])

        needs_commit = False

        if user.get_db_user() is None:
            trigger.add_aspect(triggers.NewUserAspect())
            print('Adding new user to the database {}'.format(user.id), flush=True)
            user.add_to_db()
            needs_commit = True

        bot: Bot = app.bot

        locale = user.get_locale()

        try:
            print('Handling message in new path for {}'.format(user.id), flush=True)
            # print(pprint.pformat(event, indent=2), flush=True)

            if 'message' in event:
                message = event['message']

                user.mark_message_seen()

                # print(pprint.pformat(message, indent=2), flush=True)

                # TODO: Is this the preferred way to differentiate inputs?
                #       What about messages that include attachments or other things?
                # TODO: This now works with aspects rather than inheritance, so in theory this could be done
                if 'text' in message:
                    message_text = message['text']

                    trigger = triggers.TextTrigger.extend(trigger, message_text)

                    if '@admin' in message_text:
                        trigger.add_aspect(triggers.AtAdminAspect())

                    if 'nlp' in message:
                        if 'detected_locales' in message['nlp'] and len(message['nlp']['detected_locales']) > 0:
                            # Get the locale that has the highest confidence
                            locale_entry = max(message['nlp']['detected_locales'], key=lambda x: x['confidence'])
                            trigger.add_aspect(triggers.LocaleAspect(locale_entry['locale'],
                                                                     locale_entry['confidence']))
                            locale = locale_entry['locale']

                        if 'entities' in message['nlp']:
                            entities = message['nlp']['entities']

                            if 'datetime' in entities:
                                for entity in entities['datetime']:
                                    if 'value' in entity:  # Specific date given, vs. date range
                                        # FIXME: Do we want to add range datetimes?
                                        trigger.add_aspect(triggers.DatetimeAspect(entity['value'], entity['grain']))

                    if user.is_admin() and message_text == 'sub':
                        # Simulate subscription instead
                        trigger = triggers.SubscriptionTrigger.extend(trigger)

                if app.config.get('DISABLED'):
                    if not user.is_admin():
                        if triggers.AtAdminAspect not in trigger:
                            user.send_message(TextMessage(trigger, localisation.DOWN_FOR_MAINTENANCE(locale)))

                        return

                    # sender_obj.send_text_message('Note: The bot is currently disabled')

            elif 'postback' in event:
                # print(pprint.pformat(event, indent=2), flush=True)

                user.mark_message_seen()

                if app.config.get('DISABLED'):
                    if not user.is_admin():
                        if triggers.AtAdminAspect not in trigger:
                            user.send_message(TextMessage(trigger, localisation.DOWN_FOR_MAINTENANCE(locale)))

                        return

                postback: dict = event['postback']

                payload = postback.get('payload')

                try:
                    data: dict = json.loads(payload)
                except json.JSONDecodeError:
                    raise

                trigger = triggers.PostbackTrigger.extend(trigger, data['name'], data['args'], data['kwargs'])

                # TODO: This will be cleaner if we work with intents (see komidabot.py)
                postback_obj = postbacks.lookup_postback(trigger.name)

                if postback_obj:
                    trigger = postback_obj.call_postback(trigger, *trigger.args, **trigger.kwargs)

                    if trigger is None:
                        return  # Indicates the trigger was processed
                        # TODO: Again, this will be cleaner if we work with intents (see komidabot.py)
                else:
                    get_app().bot.message_admins(TextMessage(triggers.Trigger(), 'Unknown postback type received!'))
                    user.send_message(TextMessage(trigger, localisation.ERROR_POSTBACK(locale)))
                    return
            elif 'request_thread_control' in event:
                request_thread_control: dict = event['request_thread_control']

                requested_owner_app_id = request_thread_control['requested_owner_app_id']
                metadata = request_thread_control['metadata']
                if requested_owner_app_id == 263902037430900:  # Page Inbox app id
                    # We'll allow the request
                    app.bot_interfaces['facebook']['api_interface'].post_pass_thread_control({
                        'recipient': {'id': user.id.id},
                        'target_app_id': requested_owner_app_id,
                        'metadata': metadata
                    })

                return
            elif 'pass_thread_control' in event:
                return  # Right now we don't need to handle this one
            else:
                print(pprint.pformat(event, indent=2), flush=True)

                get_app().bot.message_admins(TextMessage(triggers.Trigger(), 'Unknown message type received!'))

                return

            bot.trigger_received(trigger)

            if needs_commit:
                db.session.commit()
        except DebuggableException as e:
            app = get_app()
            app.bot.notify_error(e)

            e.print_info(app.logger)
        except Exception as e:
            try:
                app.logger.error('Error while handling event:\n{}'.format(pprint.pformat(event, indent=2)))
                get_app().bot.notify_error(e)
            except Exception:
                pass

            user.send_message(TextMessage(trigger, localisation.INTERNAL_ERROR(locale)))
            app.logger.exception(e)
示例#26
0
def handle_web_push_subscription():
    try:
        app = get_app()
        data = request.get_json()

        print(pprint.pformat(data, indent=2), flush=True)

        if data and 'subscription' in data:
            subscription = data['subscription']

            if 'endpoint' not in subscription:
                return abort(400)

            if 'keys' not in subscription:
                return abort(400)

            endpoint = subscription['endpoint']
            keys = subscription['keys']

            needs_commit = False

            user_manager = app.user_manager
            user: WebUser = user_manager.get_user(UserId(endpoint, web_constants.PROVIDER_ID))

            if user.get_db_user() is None:
                print('Adding new subscription to the database {}'.format(user.id), flush=True)
                user.add_to_db()
                user.set_data({
                    'keys': keys
                })
                needs_commit = True

            if 'days' in subscription:
                days = subscription['days']

                if len(days) != 5:
                    return abort(400)

                for i in range(5):
                    if i >= 5:
                        break

                    day = models.week_days[i]
                    campus_id = days[i]

                    campus = user.get_campus_for_day(day)

                    if campus_id is None:
                        if user.disable_subscription_for_day(day):
                            needs_commit = True
                    elif campus is None or campus.id != campus_id:
                        campus = models.Campus.get_by_id(campus_id)
                        if campus is None:
                            continue
                        user.set_campus_for_day(campus, day)
                        needs_commit = True

            if needs_commit:
                db.session.commit()

            return '{}', 200

        return abort(400)
    except DebuggableException as e:
        app = get_app()
        app.bot.notify_error(e)

        e.print_info(app.logger)

        return abort(500)
    except Exception as e:
        try:
            get_app().bot.notify_error(e)
        except Exception:
            pass

        traceback.print_tb(e.__traceback__)
        print(e, flush=True, file=sys.stderr)

        return abort(500)