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')
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.'))
def build_callback_buttons(self, buttons: List[List[dict]]): return [[{ 'text': _('⬅ Back'), 'data': 'back', }, { 'text': _('Cancel ❌'), 'data': 'cancel', }]]
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
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()
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}'
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')
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
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')
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())
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, )
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 ''
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())
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')
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)
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)
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)
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'}, ], ]
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.'))
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, }
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
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)
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')
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
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
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
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', }