예제 #1
0
class PhoneState(BaseState):
    _intro_message = BotMessage(
        text=_('Tap the button below to share your phone number or enter it.'),
        buttons=[
            [{
                'text': _('Share phone number'),
                'type': 'phone',
            }],
            [_('⬅ Back')]
        ],
        is_tg_text_buttons=True
    )

    async def get_back_state(self):
        return 'purchase' if self.user.phone else 'show'

    async def process(self):
        if self.text and self.text.startswith('⬅'):
            self.set_state(await self.get_back_state())
            return

        phone = (self.contact and self.contact.phone) or validate_phone(self.text)

        if phone is None:
            self.message_was_not_recognized = True
            return

        self.user.phone = phone
        self.set_state('purchase')
예제 #2
0
    def get_intro_message(self):
        trip_service = TripService.get_service(self.data['show'])

        stations = trip_service.get_stations()

        if stations.count() == 0:
            raise ValueError(f'There are no stations available in db for '
                             f'provider {trip_service.provider_name()}'
                             f'and direction {trip_service.direction_name()}')

        buttons = [[{'text': x.name, 'data': str(x.id)}] for x in stations]

        viber_limit_with_back = VIBER_ROWS_LIMIT - 1
        viber_limit_with_back_and_navigate = viber_limit_with_back - 1

        if len(buttons) > viber_limit_with_back:
            if self.data.get('station__last'):
                buttons = buttons[viber_limit_with_back_and_navigate:]
                buttons.insert(0, [{
                    'text': _('...previous'),
                    'data': 'first',
                }])

            else:
                buttons = buttons[:viber_limit_with_back_and_navigate]
                buttons.append([{
                    'text': _('more...'),
                    'data': 'last',
                }])

        buttons.append([BACK])
        return BotMessage(_('Choose start station:'), buttons=buttons)
    async def process(self):
        if self.text is None:
            self.message_was_not_recognized = True
            return

        self.text = self.text.strip()

        if not (3 <= len(self.text) < 10):
            self.message_was_not_recognized = True
            return

        connector = await purchase(self.user, self.data, sms_code=self.text)
        result = connector.get_result()

        if result == PurchaseResult.SUCCESS:
            await store_purchase_event(self.user, self.data)
            self.set_state('where')
            self.add_message(connector.get_message())
            return

        if result == PurchaseResult.WRONG_SMS:
            self.add_message(_('Wrong SMS. Enter again.'))
            return

        self.set_state('show')
        self.add_message(
            connector.get_message()
            or _('Failed to purchase the trip. Try another provider.'))
예제 #4
0
파일: trip.py 프로젝트: demidov91/mogiminsk
 def build_callback_buttons(self, buttons: List[List[dict]]):
     return [[{
         'text': _('⬅ Back'),
         'data': 'back',
     }, {
         'text': _('Cancel ❌'),
         'data': 'cancel',
     }]]
예제 #5
0
파일: trip.py 프로젝트: demidov91/mogiminsk
    def direction_name(self):
        if self.instance.direction == Trip.MOG_MINSK_DIRECTION:
            return _('Mogilev - Minsk')

        if self.instance.direction == Trip.MINSK_MOG_DIRECTION:
            return _('Minsk - Mogilev')

        return self.instance.direction
예제 #6
0
    async def handle_system_message(cls, remote_update: Update):
        if remote_update.event == defines.EVENT_TYPE_CONVERSATION_STARTED:
            if not remote_update.subscribed:
                return web.json_response(
                    {
                        'text':
                        _('This is a bot for booking Mogilev-Minsk minibusses.\n'
                          'Choose the date, time and book your trip!\n'
                          'Notice:\n\U0001f690 - bookable directly from bot.\n'
                          '\U0001f4de - you have to make a call to book your trip.\n'
                          ),
                        'type':
                        'text',
                        'min_api_version':
                        4,
                        "keyboard": {
                            "Type":
                            "keyboard",
                            'InputFieldState':
                            'hidden',
                            "Buttons": [
                                {
                                    "ActionType": "open-url",
                                    "ActionBody":
                                    "https://rate.pautuzin.by/static/viber_intro.gif?v=2",
                                    "Text": _("Take a quick animated tour"),
                                    'OpenURLMediaType': 'gif',
                                },
                            ],
                        },
                    },
                    dumps=lazy_string_aware_json_dumps)

            logger.debug(
                'User %s has started conversation again. Ignoring it.',
                remote_update.user.id)

        elif remote_update.event == defines.EVENT_TYPE_UNSUBSCRIBED:
            logger.info('User %s has unsubscribed.', remote_update.user.id)

        elif remote_update.event == defines.EVENT_TYPE_SUBSCRIBED:
            logger.info('User %s has subscribed.', remote_update.user.id)

        elif remote_update.event == defines.EVENT_TYPE_FAILED:
            logger.error(
                'Failed to deliver message to the user %s. Error message is: %s.',
                remote_update.user.id, remote_update.description)

        else:
            logger.error('Unexpected event: %s', remote_update.event)

        return web.Response()
예제 #7
0
    def build_purchase_label(self, purchase):
        trip_time = purchase.trip.start_datetime.strftime(TIME_FORMAT)
        trip_date = purchase.trip.start_datetime.strftime('%d.%m')
        if purchase.trip.direction == Trip.MINSK_MOG_DIRECTION:
            trip_direction = _('Minsk-Mog.')

        elif purchase.trip.direction == Trip.MOG_MINSK_DIRECTION:
            trip_direction = _('Mog.-Minsk')

        else:
            trip_direction = ''

        return f'{trip_direction} {trip_time} {trip_date}'
예제 #8
0
class TimePeriodState(BaseState):
    back = 'date'

    MORNING_END = 11
    EVENING_START = 17

    TEXT = _('When?')
    buttons = (
        {
            'text': _('\U0001f305 Morning'),
            'data': 'morning',
        },
        {
            'text': _('\U0001f31e Day'),
            'data': 'day',
        },
        {
            'text': _('\U0001f307 Evening'),
            'data': 'evening',
        },
    )

    def get_intro_message(self):
        return BotMessage(self.TEXT, [self.buttons, [BACK]])

    async def initialize(self, current_state: str):
        current_time = datetime.datetime.now()

        if current_time.date() != datetime.datetime.strptime(self.data['date'], DATE_FORMAT).date():
            return self

        if current_time.hour < self.MORNING_END:
            return self

        if self.MORNING_END <= current_time.hour < self.EVENING_START:
            self.buttons = self.buttons[1:]
            return self

        if current_state == 'time':
            return await self.create_state('date').initialize(current_state)

        self.data['timeperiod'] = 'evening'
        return await self.create_state('time').initialize(current_state)

    async def process(self):
        if self.value not in ('morning', 'day', 'evening'):
            self.message_was_not_recognized = True
            return

        self.set_state('time')
예제 #9
0
파일: trip.py 프로젝트: demidov91/mogiminsk
    async def process(self):
        if self.value == 'finish':
            self.set_state('where')
            self.add_message(
                _("I hope you've called dispatcher. "
                  "Trips with \U0001f4de icons can't be booked from bot. "
                  "Choose trip with \U0001f690 symbol to book in-app."))
            return

        if self.value.startswith('tel:'):
            self.add_message(
                _('Press "Got it" button after you\'ve purchased the trip.'))
            return

        self.message_was_not_recognized = True
예제 #10
0
class SeatState(BaseState):
    choices = tuple(range(1, 5))

    _intro_message = BotMessage(_('How many seats?'), buttons=[
        [{
            'text': str(x),
            'data': str(x),
        } for x in choices],
        [BACK]
    ])

    async def get_back_state(self):
        if self.data.get('seat'):
            return 'purchase'

        return 'show'

    async def process(self):
        try:
            seat_number = int(self.value)
        except ValueError:
            self.message_was_not_recognized = True
            return

        if seat_number not in self.choices:
            self.message_was_not_recognized = True
            return

        self.set_state('purchase')
예제 #11
0
 def handle_exception(cls, user: User) -> Iterable[BotMessage]:
     cls.set_bot_context(user, {
         'state': 'where',
         'bot': cls.BOT_CONTEXT_VALUE,
     })
     return cls.get_state(user).get_intro_message().to_sequence(
         [_('Something went wrong...')])
    def test_encode__lazy_text(self):
        from aiohttp_translation import gettext_lazy as _

        lazy_string = _('Test message')

        async def inner():
            activate('be')
            encoded = json.dumps(
                {'text': lazy_string},
                cls=LazyAwareJsonEncoder,
            )
            assert json.loads(encoded) == {
                'text': 'Шмат тэстаў не бывае',
            }

            activate('ru')
            encoded = json.dumps(
                {'text': lazy_string},
                cls=LazyAwareJsonEncoder,
            )
            assert json.loads(encoded) == {
                'text': 'Сие есть сущая тестовая строка',
            }

        asyncio.get_event_loop().run_until_complete(inner())
예제 #13
0
    def get_intro_message(self):
        if self.is_wrong_sms():
            text = _('Wrong SMS code.')
            buttons = [[BACK, {
                'text': _('Send again'),
                'data': 'resend',
            }]]

        else:
            text = _('SMS was sent to +%s. Enter it.') % self.user.phone
            buttons = [[BACK]]

        return BotMessage(
            text=text,
            buttons=buttons,
            is_text_input=True,
        )
예제 #14
0
def _build_trip_description_part_3(ts: TripService):
    if ts.instance.remaining_seats:
        if not ts.instance.is_default_price():
            return _('({} seats)').format(ts.instance.remaining_seats)

        return '({})'.format(ts.instance.remaining_seats)

    return ''
예제 #15
0
    def get_intro_message(self):
        trip_id_list = self.data['trip_id_list']
        show_shorten = (len(trip_id_list) >
                        4) and not self.data.get(FULL_TRIPS_SWITCH)

        if show_shorten:
            trip_id_list = trip_id_list[:3]

        trips = TripService.id_list(trip_id_list).order_by(Trip.start_datetime)
        buttons = [trip_to_line(trip) for trip in trips]

        if show_shorten:
            buttons.append([{
                'text': _('More'),
                'data': 'full',
            }])

        buttons.append([BACK])
        return BotMessage(text=_('Choose trip:'), buttons=buttons)
    def test_gettext_lazy(self):
        lazy_text = _('Test message')

        async def inner():
            activate('ru')
            assert str(lazy_text) == 'Сие есть сущая тестовая строка'

            activate('be')
            assert str(lazy_text) == 'Шмат тэстаў не бывае'

        asyncio.get_event_loop().run_until_complete(inner())
예제 #17
0
class NotesState(BaseState):
    back = 'purchase'

    _intro_message = BotMessage(_('Add some notes:'),
                                buttons=[[BACK]],
                                is_text_input=True)

    async def process(self):
        notes = (self.text or '').strip()
        self.data[self.get_name()] = notes
        self.set_state('purchase')
예제 #18
0
파일: date.py 프로젝트: demidov91/mogiminsk
    def __init__(self):
        today = datetime.date.today()
        tomorrow = today + datetime.timedelta(days=1)
        buttons = (
            (_('Today, %s') % format_date(today, 'E', locale=get_active()),
             today.strftime(DATE_FORMAT)),
            (_('Tomorrow, %s') %
             format_date(tomorrow, 'E', locale=get_active()),
             tomorrow.strftime(DATE_FORMAT)),
            (_('Other'), 'other'),
        )
        buttons = [
            [{
                'text': x[0],
                'data': x[1]
            } for x in buttons],
            [BACK],
        ]

        super(DateMessage, self).__init__(_('Choose the date'), buttons)
예제 #19
0
    def get_intro_message(self):
        purchases = tuple(UserService(self.user).future_purchases())
        if not purchases:
            return BotMessage(_('You have no pending trips'), buttons=[[BACK]])

        buttons = []
        for purchase in purchases:
            buttons.append([{
                'text': self.build_purchase_label(purchase),
                'data': 'show_{}'.format(purchase.id),
            }, {
                'text': b'\xE2\x9D\x8C'.decode('utf-8'),
                'data': 'cancel_{}'.format(purchase.id),
            }])

        buttons.append([BACK])

        text = _('Your purchases') if len(purchases) > 1 else _(
            'Your purchase')

        return BotMessage(text, buttons)
예제 #20
0
파일: base.py 프로젝트: demidov91/mogiminsk
    async def produce(self) -> Sequence[BotMessage]:
        next_state = await self.create_state(self.get_state()
                                             ).initialize(self.get_name())
        self.set_state(next_state.get_name())
        message = next_state.get_intro_message()
        if self.message_was_not_recognized:
            logger.warning(
                'Unexpected user response on state %s: text=%s, value=%s',
                self.get_name(), self.text, self.value)
            self.add_message(_('Unexpected response.'))

        extra_messages = self.pop_messages()
        return message.to_sequence(extra_messages)
예제 #21
0
    def get_buttons(self):
        notes = self.data.get('notes')
        notes = _('Notes: %s') % notes if notes else _('Notes')

        return [
            [{'text': _('Name: %s') % self.user.first_name, 'data': 'firstname'}],
            [{'text': _('Phone: +%s') % self.user.phone, 'data': 'phone'}],
            [{'text': _('Pick up: %s') % self.data["station_name"], 'data': 'station'}],
            [{'text': _('%s seat(s)') % self.data["seat"], 'data': 'seat'}],
            [{'text': notes, 'data': 'notes'}],
            [
                {'text': _('⬅ Back'), 'data': 'back'},
                {'text': _('Book it! ✅'), 'data': 'submit'},
            ],
        ]
예제 #22
0
    async def cancellation(self, sms_code=None):
        """
        Makes external API cancellation call to cancel self.data['purchase_cancel'] trip
            and moves current state either to CancelWithSMS or to PurchaseList.
        :param sms_code: just entered SMS-code if any.
        """
        connector = await cancel_purchase(self.user, self.data, sms_code)
        result = connector.get_result()
        user_service = UserService(self.user)
        self.set_wrong_sms(False)

        if result == CancellationResult.SUCCESS:
            user_service.delete_purchase(self.get_cancellation_trip_id())
            self.set_state('purchaselist')
            self.add_message(connector.get_message()
                             or _('Purchase was CANCELLED!'))
            return

        if result == CancellationResult.NEED_SMS:
            self.set_state('cancelpurchasewithsms')
            return

        if result == CancellationResult.WRONG_SMS:
            self.set_wrong_sms()
            self.set_state('cancelpurchasewithsms')
            return

        if result == CancellationResult.DOES_NOT_EXIST:
            self.add_message(
                _("Looks like the purchasement was already cancelled. "
                  "Call the company if you don't think so."))
            user_service.delete_purchase(self.get_cancellation_trip_id())
            self.set_state('purchaselist')
            return

        self.add_message(
            connector.get_message()
            or _('Failed to cancel. Please, call the company to cancel.'))
예제 #23
0
    def get_text(self, trip: Trip):
        trip_service = TripService(trip)

        return _(
            'Firm: %(provider)s\n'
            'Direction: %(direction)s\n'
            'Time: %(time)s\n'
            'Phone: %(phone)s\n'
            '(Tap on the buttons bellow to change)'
        ) % {
            'provider': trip_service.provider_name(),
            'direction': trip_service.direction_name(),
            'time': trip_service.instance.start_datetime,
            'phone': self.user.phone,
        }
예제 #24
0
파일: trip.py 프로젝트: demidov91/mogiminsk
    def build_text(self, text: str):
        contacts = self._get_contacts()

        contacts_message = '\n'.join(
            [f'{contact.kind}: {contact.contact}' for contact in contacts])

        text = '{}, {}, {}'.format(
            self.trip_service.provider_name(),
            self.trip_service.direction_name(),
            self.trip_service.instance.start_datetime.strftime(
                '%d.%m.%Y %H:%M'))

        if not contacts_message:
            text += '\n' + _(
                'Unfortunately I have no contacts for this trip :(')

        else:
            text += ':\n' + contacts_message

        return text
예제 #25
0
파일: date.py 프로젝트: demidov91/mogiminsk
    def __init__(self):
        today = datetime.date.today()
        weekday = today.weekday()

        first_line = [
            self._date_to_button(today + datetime.timedelta(days=x))
            for x in range(7 - weekday)
        ]
        second_line = [
            self._date_to_button(today + datetime.timedelta(days=x))
            for x in range(7 - weekday, 14 - weekday)
        ]

        buttons = [
            first_line,
            second_line,
            [BACK],
        ]

        super(OtherDateMessage, self).__init__(_('Choose the date'),
                                               buttons=buttons)
예제 #26
0
class FirstNameState(BaseState):
    async def get_back_state(self):
        if self.user.first_name:
            return 'purchase'

        return 'show'

    _intro_message = BotMessage(text=_("What's your name?"),
                                buttons=[[BACK]],
                                is_text_input=True)

    async def process(self):
        if self.text:
            self.text = self.text.strip()

        if not self.text:
            self.message_was_not_recognized = True
            return

        self.user.first_name = self.text
        self.set_state('purchase')
예제 #27
0
    async def process(self):
        bot = self.get_bot()

        logger.warning(
            'Got %s feedback!\n---\n%s\n---\n'
            'User: first_name - %s; phone - %s\n'
            'Context: %s',
            bot, self.text, self.user.first_name, self.user.phone, self.data,
            extra={
                'tags': {
                    'event': 'feedback',
                    'bot': bot,
                },
            },
        )

        if not self.text:
            if bot == TELEGRAM_BOT:
                contact = TG_CONTACT

            else:
                contact = VIBER_CONTACT

            self.add_message(_(
                "Excuse me, I can't recognize your message :( "
                "Please, contact me directly at %s to tell what you wanted."
            ) % contact)

        else:
            ConversationService.add_user_message(
                self.user,
                self.text,
                self.data,
                bot
            )

        self.data['feedback__continue'] = True
예제 #28
0
    async def process(self):
        if self.value in ('firstname', 'phone', 'station', 'seat', 'notes'):
            self.set_state(self.value)
            return

        if self.value == 'submit':
            connector = await purchase(self.user, self.data)
            purchase_result = connector.get_result()
            if purchase_result == PurchaseResult.SUCCESS:
                self.add_message(connector.get_message())
                self.set_state('where')
                await store_purchase_event(self.user, self.data)
                return

            if purchase_result == PurchaseResult.FAIL:
                self.set_state('show')
                self.add_message(_('Failed to purchase the trip. Try another provider.'))
                return

            if purchase_result == PurchaseResult.NEED_REGISTRATION:
                self.set_state('finishpurchasewithsms')
                return

        self.message_was_not_recognized = True
예제 #29
0
class FeedbackState(BaseState):
    back = 'where'

    greeting_intro_message = BotMessage(
        _('Send me some feedback. '
            'What would you improve, '
            'what went wrong while using the bot?\n'
            'It will help me become better.'),
        buttons=[[BACK]],
        is_text_input=True
    )

    continue_intro_message = BotMessage(
        _('Add anything or press "Back" to return to booking trips.'),
        buttons=[[BACK]],
        is_text_input=True
    )

    def get_intro_message(self):
        if self.data.pop('feedback__continue', False):
            return self.continue_intro_message

        return self.greeting_intro_message

    async def process(self):
        bot = self.get_bot()

        logger.warning(
            'Got %s feedback!\n---\n%s\n---\n'
            'User: first_name - %s; phone - %s\n'
            'Context: %s',
            bot, self.text, self.user.first_name, self.user.phone, self.data,
            extra={
                'tags': {
                    'event': 'feedback',
                    'bot': bot,
                },
            },
        )

        if not self.text:
            if bot == TELEGRAM_BOT:
                contact = TG_CONTACT

            else:
                contact = VIBER_CONTACT

            self.add_message(_(
                "Excuse me, I can't recognize your message :( "
                "Please, contact me directly at %s to tell what you wanted."
            ) % contact)

        else:
            ConversationService.add_user_message(
                self.user,
                self.text,
                self.data,
                bot
            )

        self.data['feedback__continue'] = True
예제 #30
0
파일: base.py 프로젝트: demidov91/mogiminsk
    def get_viber_buttons(self) -> List[dict]:
        if not self.buttons:
            return None

        viber_buttons = []
        for row in self.buttons:
            viber_width = VIBER_BUTTONS_WIDTH // len(row)

            for button in row:
                button_with_width = defaultdict(dict, button)
                button_with_width['viber']['Columns'] = viber_width
                viber_buttons.append(button_with_width)

        return viber_buttons

    def __str__(self):
        return '{}: {}'.format(type(self), {
            'text': self.text,
            'buttons': self.buttons,
        })

    def __repr__(self):
        return str(self)


# Standard 'Back' button.
BACK = {
    'text': _('Back'),
    'data': 'back',
}