def test_switch_mode(self): bot_user = BotUser(**self.default_user_data) bot = EvernoteBot(self.config) with self.assertRaises(EvernoteBotException) as ctx: bot.switch_mode(bot_user, "invalid") self.assertEqual(str(ctx.exception), "Unknown mode 'invalid'") bot.api = mock.Mock() bot.api.sendMessage = mock.Mock() bot.switch_mode(bot_user, "multiple_notes") bot.api.sendMessage.assert_called_once_with( 1, "The bot already in 'multiple_notes' mode.", "{\"hide_keyboard\": true}") bot.switch_mode_one_note = mock.Mock() bot.switch_mode(bot_user, "one_note") bot.switch_mode_one_note.assert_called_once() bot_user.bot_mode = "one_note" bot_user.evernote.shared_note_id = 123 bot.api.sendMessage = mock.Mock() bot.switch_mode(bot_user, "multiple_notes") bot.api.sendMessage.assert_called_once_with( 1, "The bot has switched to 'multiple_notes' mode.", "{\"hide_keyboard\": true}") self.assertIsNone(bot_user.evernote.shared_note_id) self.assertEqual(bot_user.bot_mode, "multiple_notes")
def start_command(bot, message: Message): user_id = message.from_user.id user_data = bot.users.get(user_id) if not user_data: current_time = time() telegram_user = message.from_user user_data = { 'id': user_id, 'created': current_time, 'last_request_ts': current_time, 'bot_mode': bot.config.get('default_mode', 'multiple_notes'), 'telegram': { 'first_name': telegram_user.first_name, 'last_name': telegram_user.last_name, 'username': telegram_user.username, 'chat_id': message.chat.id, }, 'evernote': { 'access': { 'permission': 'basic' }, }, } bot.users.create(user_data) user = BotUser(**user_data) message_text = '''Welcome! It's bot for saving your notes to Evernote on fly. Please tap on button below to link your Evernote account with bot.''' user.evernote.oauth = get_evernote_oauth_data(bot, user, message_text) bot.users.save(user.asdict())
def handle_state(self, bot_user: BotUser, message: Message): state = bot_user.state handlers_map = { 'switch_mode': self.switch_mode, # self.switch_mode() 'switch_notebook': self.switch_notebook, # self.switch_notebook() } state_handler = handlers_map[state] state_handler(bot_user, message.text) bot_user.state = None self.users.save(bot_user.asdict())
def handle_state(self, bot_user: BotUser, message: Message): state = bot_user.state handlers_map = { "switch_mode": self.switch_mode, # self.switch_mode() "switch_notebook": self.switch_notebook, # self.switch_notebook() } state_handler = handlers_map.get(state) if not state_handler: raise EvernoteBotException(f"Invalid state: {state}") state_handler(bot_user, message.text) bot_user.state = None self.users.save(bot_user.asdict())
def evernote_oauth_callback(bot, callback_key: str, oauth_verifier: str, access_type: str = "basic"): query = {"evernote.oauth.callback_key": callback_key} user_data = bot.users.get(query, fail_if_not_exists=True) user = BotUser(**user_data) chat_id = user.telegram.chat_id if not oauth_verifier: bot.api.sendMessage( chat_id, "We are sorry, but you have declined " "authorization.") return evernote_config = bot.config["evernote"]["access"][access_type] oauth = user.evernote.oauth try: oauth_params = { "token": oauth.token, "secret": oauth.secret, "verifier": oauth_verifier, } user.evernote.access.token = bot.evernote().get_access_token( evernote_config["key"], evernote_config["secret"], sandbox=bot.config.get("debug", True), **oauth_params) except TokenRequestDenied as e: bot.api.sendMessage( chat_id, "We are sorry, but we have some problems " "with Evernote connection. " "Please try again later.") raise e except Exception as e: bot.api.sendMessage(chat_id, "Unknown error. Please, try again later.") raise e user.evernote.access.permission = access_type user.evernote.oauth = None if access_type == "basic": bot.api.sendMessage( chat_id, "Evernote account is connected.\nFrom now " "you can just send a message and a note will be created.") default_notebook = bot.evernote(user).get_default_notebook() user.evernote.notebook = EvernoteNotebook(**default_notebook) mode = user.bot_mode.replace("_", " ").capitalize() bot.api.sendMessage( chat_id, "Current notebook: " f"{user.evernote.notebook.name}\nCurrent mode: {mode}") else: bot.switch_mode(user, "one_note") bot.users.save(user.asdict())
def test_evernote_oauth_declined_auth(self): callback_key = hashlib.sha1(b"xxx").hexdigest() request = self.create_request({"key": callback_key, "access": "basic"}) bot = request.app.bot bot.api = mock.Mock() bot.api.sendMessage = mock.Mock() bot_user = BotUser(**self.default_user_data) bot_user.evernote.oauth = EvernoteOauthData(token="token", secret="secret", callback_key=callback_key) bot.users.create(bot_user.asdict()) evernote_oauth(request) bot.api.sendMessage.assert_called_once() self.assertEqual(bot.api.sendMessage.call_args[0][1], "We are sorry, but you have declined authorization.")
def test_switch_notebook(self): bot_user = BotUser(**self.default_user_data) bot = EvernoteBot(self.config) bot.api = mock.Mock() bot.api.sendMessage = mock.Mock() bot.evernote = mock.Mock() all_notebooks = [ { "guid": "xxx", "name": "xxx" }, { "guid": "zzz", "name": "zzz" }, ] bot.evernote().get_all_notebooks = lambda query: list( filter(lambda nb: nb["name"] == query["name"], all_notebooks)) with self.assertRaises(EvernoteBotException) as ctx: bot.switch_notebook(bot_user, "> www <") self.assertEqual(str(ctx.exception), "Notebook 'www' not found") bot.switch_notebook(bot_user, "zzz") self.assertEqual(bot_user.evernote.notebook.name, "zzz") self.assertEqual(bot_user.evernote.notebook.guid, "zzz") bot.api.sendMessage.assert_called_once() bot.switch_notebook(bot_user, "xxx") self.assertEqual(bot_user.evernote.notebook.guid, "xxx") bot.switch_notebook(bot_user, "xxx") self.assertEqual(bot_user.evernote.notebook.guid, "xxx")
def evernote_oauth_callback(bot, params: OauthParams): query = {'evernote.oauth.callback_key': params.callback_key} user_data = bot.users.get(query, fail_if_not_exists=True) user = BotUser(**user_data) chat_id = user.telegram.chat_id if not params.verifier: bot.api.sendMessage( chat_id, 'We are sorry, but you have declined ' 'authorization.') return evernote_config = bot.config['evernote']['access'][params.access_type] oauth = user.evernote.oauth try: oauth_params = { 'token': oauth.token, 'secret': oauth.secret, 'verifier': params.verifier, } user.evernote.access.token = bot.evernote().get_access_token( evernote_config['key'], evernote_config['secret'], sandbox=bot.config.get('debug', bot.config['debug']), **oauth_params) except TokenRequestDenied as e: bot.api.sendMessage( chat_id, 'We are sorry, but we have some problems ' 'with Evernote connection. ' 'Please try again later.') raise e except Exception as e: bot.api.sendMessage(chat_id, "Unknown error. Please, try again later.") raise e user.evernote.access.permission = params.access_type user.evernote.oauth = None if params.access_type == "basic": bot.api.sendMessage( chat_id, "Evernote account is connected.\nFrom now " "you can just send a message and a note will be created.") default_notebook = bot.evernote(user).get_default_notebook() user.evernote.notebook = EvernoteNotebook(**default_notebook) mode = user.bot_mode.replace("_", " ").capitalize() bot.api.sendMessage( chat_id, "Current notebook: " f"{user.evernote.notebook.name}\nCurrent mode: {mode}") else: bot.switch_mode(user, "one_note") bot.users.save(user.asdict())
def start_command(bot, message: dict): user_id = message['from']['id'] user_data = bot.users.get(user_id) if not user_data: current_time = time() telegram_user = message['from'] user_data = { 'id': user_id, 'created': current_time, 'last_request_ts': current_time, 'bot_mode': 'multiple_notes', 'telegram': { 'first_name': telegram_user['first_name'], 'last_name': telegram_user['last_name'], 'username': telegram_user['username'], 'chat_id': message['chat']['id'], }, 'evernote': { 'access': {'permission': 'basic'}, }, } bot.users.create(user_data) user = BotUser(**user_data) message_text = '''Welcome! It's bot for saving your notes to Evernote on fly. Please tap on button below to link your Evernote account with bot.''' chat_id = user.telegram.chat_id auth_button = {'text': 'Waiting for Evernote...', 'url': bot.url} inline_keyboard = json.dumps({'inline_keyboard': [[auth_button]]}) status_message = bot.api.sendMessage(chat_id, message_text, inline_keyboard) key = bot.config['evernote']['access']['basic']['key'] secret = bot.config['evernote']['access']['basic']['secret'] oauth_callback = bot.config['oauth_callback'] oauth_data = evernote.get_oauth_data(user.id, key, secret, oauth_callback, sandbox=bot.config.get('debug')) auth_button['text'] = 'Sign in with Evernote' auth_button['url'] = oauth_data['oauth_url'] inline_keyboard = json.dumps({'inline_keyboard': [[auth_button]]}) bot.api.editMessageReplyMarkup(chat_id, status_message['message_id'], inline_keyboard) user.evernote.oauth = EvernoteOauthData( token=oauth_data['oauth_token'], secret=oauth_data['oauth_token_secret'], callback_key=oauth_data['callback_key'] ) bot.users.save(user.asdict())
def on_text(self, message: Message): user_data = self.users.get(message.from_user.id) user = BotUser(**user_data) text = message.text telegram_link = message.get_telegram_link() if telegram_link: text = f'<div><p><a href="{telegram_link}">{telegram_link}</a></p><pre>{text}</pre></div>' title = self.get_caption(message) or '[Telegram bot]' self.save_note(user, '', title=title, html=text)
def test_get_evernote_api_object(self, sdk): bot = EvernoteBot(bot_config) api = bot.evernote() self.assertIsNotNone(api) self.assertTrue("default" in bot._evernote_apis_cache) bot_user = BotUser(**self.default_user_data) api = bot.evernote(bot_user) self.assertIsNotNone(api) self.assertEqual(len(bot._evernote_apis_cache), 2) self.assertTrue(bot_user.id in bot._evernote_apis_cache) for i in range(110): self.default_user_data["id"] = i bot_user = BotUser(**self.default_user_data) api = bot.evernote(bot_user) self.assertIsNotNone(api) self.assertEqual(len(bot._evernote_apis_cache), 100) self.assertFalse("default" in bot._evernote_apis_cache) self.assertFalse(1 in bot._evernote_apis_cache)
def switch_mode_command(bot, message: Message): mode = message.text user_id = message.from_user.id user_data = bot.users.get(user_id) user = BotUser(**user_data) buttons = [] for mode in ('one_note', 'multiple_notes'): title = mode.capitalize().replace('_', ' ') if user.bot_mode == mode: title = f'> {title} <' buttons.append({'text': title}) keyboard = { 'keyboard': [[b] for b in buttons], 'resize_keyboard': True, 'one_time_keyboard': True, } bot.api.sendMessage(user.telegram.chat_id, 'Please, select mode', json.dumps(keyboard)) user.state = 'switch_mode' bot.users.save(user.asdict())
def _save_file_to_evernote(self, file_id, file_size, message: Message): max_size = 20 * 1024 * 1024 # telegram restriction. We can't download any file that has size more than 20Mb if file_size > max_size: raise EvernoteBotException('File too big. Telegram does not allow to the bot to download files over 20Mb.') filename, short_name = download_telegram_file(self.api, file_id, self.config["tmp_root"]) user_data = self.users.get(message.from_user.id) user = BotUser(**user_data) self._check_evernote_quota(user, file_size) title = message.caption or message.text[:20] or 'File' files = ({'path': filename, 'name': short_name},) self.save_note(user, text=message.text, title=title, files=files)
def switch_mode_command(bot, message: dict): user_id = message['from']['id'] user_data = bot.users.get(user_id) if not user_data: raise TelegramBotError("Unregistered user {0}. You've to send /start to register.") user = BotUser(**user_data) buttons = [] for mode in ('one_note', 'multiple_notes'): title = mode.capitalize().replace('_', ' ') if user.bot_mode == mode: title = f'> {title} <' buttons.append({'text': title}) keyboard = { 'keyboard': [[b] for b in buttons], 'resize_keyboard': True, 'one_time_keyboard': True, } bot.api.sendMessage(user.telegram.chat_id, 'Please, select mode', json.dumps(keyboard)) user.state = 'switch_mode' bot.users.save(user.asdict())
def switch_notebook_command(bot, message: Message): user_id = message.from_user.id user_data = bot.users.get(user_id) user = BotUser(**user_data) all_notebooks = bot.evernote.get_all_notebooks(user.evernote.access.token) buttons = [] for notebook in all_notebooks: name = notebook["name"] if name == user.evernote.notebook.name: name = f"> {name} <" buttons.append({"text": name}) keyboard = { "keyboard": [[b] for b in buttons], "resize_keyboard": True, "one_time_keyboard": True, } bot.api.sendMessage(user.telegram.chat_id, "Please, select notebook", json.dumps(keyboard)) user.state = "switch_notebook" bot.users.save(user.asdict())
def get_evernote_api(self, user_id: int = None): user_id = user_id or self.ctx.get('user_id') if not user_id: raise Exception('`user_id` is not set') if not self._evernote_api.get(user_id): user_data = self.users.get(user_id) user = BotUser(**user_data) token = user.evernote.access.token self._evernote_api[user_id] = EvernoteApi( token, sandbox=self.config['debug']) return self._evernote_api[user_id]
def switch_notebook_command(bot, message: dict): user_id = message['from']['id'] user_data = bot.users.get(user_id) if not user_data: raise TelegramBotError("Unregistered user {0}. You've to send /start to register.") user = BotUser(**user_data) evernote_api = bot.get_evernote_api(user_id) all_notebooks = evernote_api.get_all_notebooks() buttons = [] for notebook in all_notebooks: name = notebook['name'] if name == user.evernote.notebook.name: name = f'> {name} <' buttons.append({'text': name}) keyboard = { 'keyboard': [[b] for b in buttons], 'resize_keyboard': True, 'one_time_keyboard': True, } bot.api.sendMessage(user.telegram.chat_id, 'Please, select notebook', json.dumps(keyboard)) user.state = 'switch_notebook' bot.users.save(user.asdict())
def on_message(self, bot, message: Message): user_id = message.from_user.id user_data = self.users.get(user_id) if not user_data: raise EvernoteBotException(f"Unregistered user {user_id}. " "You've to send /start command to register") bot_user = BotUser(**user_data) if not bot_user.evernote or not bot_user.evernote.access.token: raise EvernoteBotException("You have to sign in to Evernote first. " "Send /start and press the button") if bot_user.state: self.handle_state(bot_user, message) else: self.handle_message(message)
def switch_mode(self, bot_user: BotUser, selected_mode_str: str): new_mode, new_mode_title = self._validate_mode(selected_mode_str) if bot_user.bot_mode == new_mode: text = f'The bot already in `{new_mode_title}` mode.' self.send_message(text) return if new_mode == 'one_note': self.switch_mode_one_note(bot_user) elif new_mode == 'multiple_notes': bot_user.evernote.shared_note_id = None bot_user.bot_mode = new_mode self.send_message( f'The bot has switched to `{new_mode_title}` mode.') raise EvernoteBotException(f'Unknown mode `{new_mode}`')
def switch_mode(self, bot_user: BotUser, selected_mode_str: str): new_mode, new_mode_title = self._validate_mode(selected_mode_str) chat_id = bot_user.telegram.chat_id if bot_user.bot_mode == new_mode: text = f"The bot already in '{new_mode_title}' mode." self.api.sendMessage(chat_id, text, json.dumps({"hide_keyboard": True})) return if new_mode == "one_note": self.switch_mode_one_note(bot_user) return # switching to 'multiple_notes' mode bot_user.evernote.shared_note_id = None bot_user.bot_mode = new_mode text = f"The bot has switched to '{new_mode_title}' mode." self.api.sendMessage(chat_id, text, json.dumps({"hide_keyboard": True}))
def _save_file_to_evernote(self, file_id, file_size, message: Message): max_size = 20 * 1024 * 1024 # telegram restriction. We can't download any file that has size more than 20Mb if file_size > max_size: raise EvernoteBotException('File too big. Telegram does not allow to the bot to download files over 20Mb.') filename, short_name = download_telegram_file(self.api, file_id, self.config["tmp_root"]) user_data = self.users.get(message.from_user.id) user = BotUser(**user_data) self._check_evernote_quota(user, file_size) title = self.get_caption(message) or (message.text and message.text[:20]) or 'File' files = ({'path': filename, 'name': short_name},) text = '' telegram_link = message.get_telegram_link() if telegram_link: text = f'<div><p><a href="{telegram_link}">{telegram_link}</a></p><pre>{message.caption}</pre></div>' self.save_note(user, '', title=title, files=files, html=text)
def on_location(self, message: Message): latitude = message.location.latitude longitude = message.location.longitude maps_url = f"https://maps.google.com/maps?q={latitude},{longitude}" title = "Location" html = f"<a href='{maps_url}'>{maps_url}</a>" if message.venue: venue = message.venue title=venue.title or title address=venue.address html = f"{title}<br />{address}<br /><a href='{maps_url}'>{maps_url}</a>" foursquare_id = venue.foursquare_id if foursquare_id: url = f"https://foursquare.com/v/{foursquare_id}" html += f"<br /><a href='{url}'>{url}</a>" user_data = self.users.get(message.from_user.id) user = BotUser(**user_data) self.save_note(user, title=title, html=html)
def switch_mode_one_note(self, bot_user: BotUser): chat_id = bot_user.telegram.chat_id evernote_data = bot_user.evernote if evernote_data.access.permission == 'full': note = self.evernote(bot_user).create_note( evernote_data.notebook.guid, title='Telegram bot notes' ) bot_user.bot_mode = 'one_note' # TODO: move up evernote_data.shared_note_id = note.guid note_url = self.evernote(bot_user).get_note_link(note.guid) text = f'Your notes will be saved to <a href="{note_url}">this note</a>' self.api.sendMessage(chat_id, text, json.dumps({'hide_keyboard': True}), parse_mode='Html') else: text = 'To enable "One note" mode you have to allow to the bot both reading and updating your notes' self.api.sendMessage(chat_id, text, json.dumps({'hide_keyboard': True})) message_text = 'Please, sign in and give the permissions to the bot.' bot_user.evernote.oauth = get_evernote_oauth_data(self, bot_user, message_text, access='full')
def on_location(self, message: Message): latitude = message.location.latitude longitude = message.location.longitude maps_url = f'https://maps.google.com/maps?q={latitude},{longitude}' title = 'Location' html = f'<a href="{maps_url}">{maps_url}</a>' if message.venue: venue = message.venue title = venue.title or title address = venue.address html = f'{title}<br />{address}<br /><a href="{maps_url}">{maps_url}</a>' foursquare_id = venue.foursquare_id if foursquare_id: url = f'https://foursquare.com/v/{foursquare_id}' html += f'<br /><a href="{url}">{url}</a>' user_data = self.users.get(message.from_user.id) user = BotUser(**user_data) title = self.get_caption(message) or title self.save_note(user, title=title, html=html)
def on_text(self, message: Message): def format_html(message: Message): if not message.entities: return message.text pointer = 0 strings = [] for entity in message.entities: strings.append(message.get_text(pointer, entity.offset)) start, end = entity.offset, entity.offset + entity.length if start < pointer: continue string = message.get_text(start, end) if entity.type == 'text_link': url = entity.url html = f'<a href="{url}">{string}</a>' elif entity.type == 'pre': html = f'<pre>{string}</pre>' elif entity.type == 'bold': html = f'<b>{string}</b>' elif entity.type == 'italic': html = f'<i>{string}</i>' elif entity.type == 'underline': html = f'<u>{string}</u>' elif entity.type == 'strikethrough': html = f'<s>{string}</s>' else: html = string strings.append(html) pointer = end strings.append(message.get_text(pointer)) text = ''.join(strings) text = '<br />'.join(text.split('\n')) return text user_data = self.users.get(message.from_user.id) user = BotUser(**user_data) html = format_html(message) telegram_link = message.get_telegram_link() if telegram_link: html = f'<div><p><a href="{telegram_link}">{telegram_link}</a></p>{html}</div>' title = self.get_caption(message) or '[Telegram bot]' self.save_note(user, '', title=title, html=html)
def on_text(self, message: Message): user_data = self.users.get(message.from_user.id) user = BotUser(**user_data) text = message.text self.save_note(user, text, title=text[:20])