diff --git a/.gitignore b/.gitignore index 6f24ee0a7..6bf9c4b33 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,10 @@ target/ # Sublime Text 2 *.sublime* + +# unitests files +telegram.mp3 +telegram.mp4 +telegram.ogg +telegram.png +telegram.webp diff --git a/.travis.yml b/.travis.yml index e5a204d01..0b683c68a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: install: - pip install pylint flake8 coveralls - pip install -r requirements.txt + - 'if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi' script: - nosetests --with-coverage --cover-package telegram/ - flake8 telegram diff --git a/AUTHORS.rst b/AUTHORS.rst index b7d583161..cc898d1fc 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -19,6 +19,7 @@ The following wonderful people contributed directly or indirectly to this projec - `macrojames `_ - `naveenvhegde `_ - `njittam `_ +- `Noam Meltzer `_ - `Rahiel Kasim `_ - `sooyhwang `_ - `wjt `_ diff --git a/telegram/bot.py b/telegram/bot.py index eee2c41c9..e520cb5b4 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -52,7 +52,7 @@ class Bot(TelegramObject): def __init__(self, token, base_url=None): - self.token = token + self.token = self._valid_token(token) if base_url is None: self.base_url = 'https://api.telegram.org/bot%s' % self.token @@ -744,3 +744,11 @@ class Bot(TelegramObject): def __reduce__(self): return (self.__class__, (self.token, self.base_url.replace(self.token, ''))) + + @staticmethod + def _valid_token(token): + """a very basic validation on token""" + left, sep, _right = token.partition(':') + if (not sep) or (not left.isdigit()) or (len(left) < 3): + raise TelegramError('Invalid token') + return token diff --git a/telegram/error.py b/telegram/error.py index fe557771b..b24bffc8d 100644 --- a/telegram/error.py +++ b/telegram/error.py @@ -18,7 +18,22 @@ """This module contains a object that represents a Telegram Error""" -import re + +def _lstrip_str(in_s, lstr): + """ + Args: + in_s (str): in string + lstr (str): substr to strip from left side + + Returns: + str: + + """ + if in_s.startswith(lstr): + res = in_s[len(lstr):] + else: + res = in_s + return res class TelegramError(Exception): @@ -26,16 +41,20 @@ class TelegramError(Exception): def __init__(self, message): """ + Args: + message (str): + Returns: - str: + """ super(TelegramError, self).__init__() - api_error = re.match(r'^Error: (?P.*)', message) - if api_error: - self.message = api_error.group('message').capitalize() - else: - self.message = message + msg = _lstrip_str(message, 'Error: ') + msg = _lstrip_str(msg, '[Error]: ') + if msg != message: + # api_error - capitalize the msg... + msg = msg.capitalize() + self.message = msg def __str__(self): return '%s' % (self.message) diff --git a/telegram/updater.py b/telegram/updater.py index 0014e7764..932b8f377 100644 --- a/telegram/updater.py +++ b/telegram/updater.py @@ -64,15 +64,26 @@ class Updater: Attributes: Args: - token (str): The bots token given by the @BotFather + token (Optional[str]): The bot's token given by the @BotFather base_url (Optional[str]): workers (Optional[int]): Amount of threads in the thread pool for functions decorated with @run_async + bot (Optional[Bot]): + + Raises: + ValueError: If both `token` and `bot` are passed or none of them. """ - def __init__(self, token, base_url=None, workers=4): + def __init__(self, token=None, base_url=None, workers=4, bot=None): + if (token is None) and (bot is None): + raise ValueError('`token` or `bot` must be passed') + if (token is not None) and (bot is not None): + raise ValueError('`token` and `bot` are mutually exclusive') - self.bot = Bot(token, base_url) + if bot is not None: + self.bot = bot + else: + self.bot = Bot(token, base_url) self.update_queue = Queue() self.dispatcher = Dispatcher(self.bot, self.update_queue, workers=workers) diff --git a/telegram/utils/request.py b/telegram/utils/request.py index fb6b3e555..a81c04af8 100644 --- a/telegram/utils/request.py +++ b/telegram/utils/request.py @@ -19,6 +19,7 @@ """This module contains methods to make POST and GET requests""" +import functools import json import socket from ssl import SSLError @@ -45,7 +46,11 @@ def _parse(json_data): Returns: A JSON parsed as Python dict with results. """ - data = json.loads(json_data.decode()) + decoded_s = json_data.decode('utf-8') + try: + data = json.loads(decoded_s) + except ValueError: + raise TelegramError('Invalid server response') if not data.get('ok') and data.get('description'): return data['description'] @@ -53,6 +58,34 @@ def _parse(json_data): return data['result'] +def _try_except_req(func): + """Decorator for requests to handle known exceptions""" + @functools.wraps(func) + def decorator(*args, **kwargs): + try: + return func(*args, **kwargs) + except HTTPError as error: + if error.getcode() == 403: + raise TelegramError('Unauthorized') + if error.getcode() == 502: + raise TelegramError('Bad Gateway') + + try: + message = _parse(error.read()) + except ValueError: + message = 'Unknown HTTPError {0}'.format(error.getcode()) + + raise TelegramError(message) + except (SSLError, socket.timeout) as error: + if "operation timed out" in str(error): + raise TelegramError("Timed out") + + raise TelegramError(str(error)) + + return decorator + + +@_try_except_req def get(url): """Request an URL. Args: @@ -67,6 +100,7 @@ def get(url): return _parse(result) +@_try_except_req def post(url, data, network_delay=2.): @@ -91,39 +125,22 @@ def post(url, else: timeout = None - try: - if InputFile.is_inputfile(data): - data = InputFile(data) - request = Request(url, - data=data.to_form(), - headers=data.headers) - else: - data = json.dumps(data) - request = Request(url, - data=data.encode(), - headers={'Content-Type': 'application/json'}) + if InputFile.is_inputfile(data): + data = InputFile(data) + request = Request(url, + data=data.to_form(), + headers=data.headers) + else: + data = json.dumps(data) + request = Request(url, + data=data.encode(), + headers={'Content-Type': 'application/json'}) - result = urlopen(request, timeout=timeout).read() - except HTTPError as error: - if error.getcode() == 403: - raise TelegramError('Unauthorized') - if error.getcode() == 502: - raise TelegramError('Bad Gateway') - - try: - message = _parse(error.read()) - except ValueError: - message = 'Unknown HTTPError' - - raise TelegramError(message) - except (SSLError, socket.timeout) as error: - if "operation timed out" in str(error): - raise TelegramError("Timed out") - - raise TelegramError(str(error)) + result = urlopen(request, timeout=timeout).read() return _parse(result) +@_try_except_req def download(url, filename): """Download a file by its URL. diff --git a/tests/test_bot.py b/tests/test_bot.py index 6ffc0ee56..3bb18213b 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -20,9 +20,14 @@ """This module contains a object that represents Tests for Telegram Bot""" import os -import unittest from datetime import datetime import sys + +if sys.version_info[0:2] == (2, 6): + import unittest2 as unittest +else: + import unittest + sys.path.append('.') import telegram @@ -137,5 +142,33 @@ class BotTest(BaseTest, unittest.TestCase): self.assertTrue(self.is_json(upf.to_json())) self.assertEqual(upf.photos[0][0].file_size, 12421) + def _test_invalid_token(self, token): + print('Testing invalid token: {0}'.format(token)) + self.assertRaisesRegexp(telegram.TelegramError, 'Invalid token', telegram.Bot, token) + + def testInvalidToken1(self): + self._test_invalid_token('123') + + def testInvalidToken2(self): + self._test_invalid_token('12a:') + + def testInvalidToken3(self): + self._test_invalid_token('12:') + + def testUnauthToken(self): + print('Testing unauthorized token') + with self.assertRaisesRegexp(telegram.TelegramError, 'Unauthorized'): + bot = telegram.Bot('1234:abcd1234') + bot.getMe() + + def testInvalidSrvResp(self): + print('Testing invalid server response') + with self.assertRaisesRegexp(telegram.TelegramError, 'Invalid server response'): + # bypass the valid token check + bot_cls = type('bot_cls', (telegram.Bot, ), {'_valid_token': lambda self, token: token}) + bot = bot_cls('12') + bot.getMe() + + if __name__ == '__main__': unittest.main() diff --git a/tests/test_updater.py b/tests/test_updater.py index 6adc7bd8b..f73d1fa8d 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -22,7 +22,6 @@ This module contains a object that represents Tests for Updater, Dispatcher, WebhookServer and WebhookHandler """ import logging -import unittest import sys import re import os @@ -31,6 +30,11 @@ from random import randrange from time import sleep from datetime import datetime +if sys.version_info[0:2] == (2, 6): + import unittest2 as unittest +else: + import unittest + try: from urllib2 import urlopen, Request except ImportError: @@ -38,7 +42,7 @@ except ImportError: sys.path.append('.') -from telegram import Update, Message, TelegramError, User, Chat, Updater +from telegram import Update, Message, TelegramError, User, Chat, Updater, Bot from telegram.dispatcher import run_async from tests.base import BaseTest from threading import Lock, Thread @@ -61,14 +65,18 @@ class UpdaterTest(BaseTest, unittest.TestCase): """ def setUp(self): - self.updater = Updater('', workers=2) - + self.updater = None self.received_message = None self.message_count = 0 self.lock = Lock() + def _setup_updater(self, *args, **kwargs): + bot = MockBot(*args, **kwargs) + self.updater = Updater(workers=2, bot=bot) + def tearDown(self): - self.updater.stop() + if self.updater is not None: + self.updater.stop() def reset(self): self.message_count = 0 @@ -119,8 +127,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_addRemoveTelegramMessageHandler(self): print('Testing add/removeTelegramMessageHandler') - bot = MockBot('Test') - self.updater.bot = bot + self._setup_updater('Test') d = self.updater.dispatcher d.addTelegramMessageHandler( self.telegramHandlerTest) @@ -132,13 +139,13 @@ class UpdaterTest(BaseTest, unittest.TestCase): d.removeTelegramMessageHandler(self.telegramHandlerTest) self.reset() - bot.send_messages = 1 + self.updater.bot.send_messages = 1 sleep(.1) self.assertTrue(None is self.received_message) def test_addTelegramMessageHandlerMultipleMessages(self): print('Testing addTelegramMessageHandler and send 100 messages...') - self.updater.bot = MockBot('Multiple', 100) + self._setup_updater('Multiple', 100) self.updater.dispatcher.addTelegramMessageHandler( self.telegramHandlerTest) self.updater.start_polling(0.0) @@ -148,8 +155,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_addRemoveTelegramRegexHandler(self): print('Testing add/removeStringRegexHandler') - bot = MockBot('Test2') - self.updater.bot = bot + self._setup_updater('Test2') d = self.updater.dispatcher regobj = re.compile('Te.*') self.updater.dispatcher.addTelegramRegexHandler(regobj, @@ -162,14 +168,13 @@ class UpdaterTest(BaseTest, unittest.TestCase): d.removeTelegramRegexHandler(regobj, self.telegramHandlerTest) self.reset() - bot.send_messages = 1 + self.updater.bot.send_messages = 1 sleep(.1) self.assertTrue(None is self.received_message) def test_addRemoveTelegramCommandHandler(self): print('Testing add/removeTelegramCommandHandler') - bot = MockBot('/test') - self.updater.bot = bot + self._setup_updater('/test') d = self.updater.dispatcher self.updater.dispatcher.addTelegramCommandHandler( 'test', self.telegramHandlerTest) @@ -181,14 +186,13 @@ class UpdaterTest(BaseTest, unittest.TestCase): d.removeTelegramCommandHandler('test', self.telegramHandlerTest) self.reset() - bot.send_messages = 1 + self.updater.bot.send_messages = 1 sleep(.1) self.assertTrue(None is self.received_message) def test_addRemoveUnknownTelegramCommandHandler(self): print('Testing add/removeUnknownTelegramCommandHandler') - bot = MockBot('/test2') - self.updater.bot = bot + self._setup_updater('/test2') d = self.updater.dispatcher self.updater.dispatcher.addUnknownTelegramCommandHandler( self.telegramHandlerTest) @@ -200,14 +204,13 @@ class UpdaterTest(BaseTest, unittest.TestCase): d.removeUnknownTelegramCommandHandler(self.telegramHandlerTest) self.reset() - bot.send_messages = 1 + self.updater.bot.send_messages = 1 sleep(.1) self.assertTrue(None is self.received_message) def test_addRemoveStringRegexHandler(self): print('Testing add/removeStringRegexHandler') - bot = MockBot('', messages=0) - self.updater.bot = bot + self._setup_updater('', messages=0) d = self.updater.dispatcher d.addStringRegexHandler('Te.*', self.stringHandlerTest) queue = self.updater.start_polling(0.01) @@ -225,8 +228,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_addRemoveStringCommandHandler(self): print('Testing add/removeStringCommandHandler') - bot = MockBot('', messages=0) - self.updater.bot = bot + self._setup_updater('', messages=0) d = self.updater.dispatcher d.addStringCommandHandler( 'test3', self.stringHandlerTest) @@ -246,8 +248,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_addRemoveUnknownStringCommandHandler(self): print('Testing add/removeUnknownStringCommandHandler') - bot = MockBot('/test') - self.updater.bot = bot + self._setup_updater('/test') d = self.updater.dispatcher d.addUnknownStringCommandHandler( self.stringHandlerTest) @@ -260,14 +261,13 @@ class UpdaterTest(BaseTest, unittest.TestCase): d.removeUnknownStringCommandHandler(self.stringHandlerTest) self.reset() - bot.send_messages = 1 + self.updater.bot.send_messages = 1 sleep(.1) self.assertTrue(None is self.received_message) def test_addRemoveErrorHandler(self): print('Testing add/removeErrorHandler') - bot = MockBot('', messages=0) - self.updater.bot = bot + self._setup_updater('', messages=0) d = self.updater.dispatcher d.addErrorHandler(self.errorHandlerTest) queue = self.updater.start_polling(0.01) @@ -286,8 +286,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_errorInHandler(self): print('Testing error in Handler') - bot = MockBot('', messages=0) - self.updater.bot = bot + self._setup_updater('', messages=0) d = self.updater.dispatcher d.addStringRegexHandler('.*', self.errorRaisingHandlerTest) @@ -300,8 +299,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_errorOnGetUpdates(self): print('Testing error on getUpdates') - bot = MockBot('', raise_error=True) - self.updater.bot = bot + self._setup_updater('', raise_error=True) d = self.updater.dispatcher d.addErrorHandler(self.errorHandlerTest) self.updater.start_polling(0.01) @@ -310,8 +308,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_addRemoveTypeHandler(self): print('Testing add/removeTypeHandler') - bot = MockBot('', messages=0) - self.updater.bot = bot + self._setup_updater('', messages=0) d = self.updater.dispatcher d.addTypeHandler(dict, self.stringHandlerTest) queue = self.updater.start_polling(0.01) @@ -330,8 +327,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_runAsync(self): print('Testing @run_async') - bot = MockBot('Test5', messages=2) - self.updater.bot = bot + self._setup_updater('Test5', messages=2) d = self.updater.dispatcher d.addTelegramMessageHandler( self.asyncHandlerTest) @@ -342,7 +338,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_additionalArgs(self): print('Testing additional arguments for handlers') - self.updater.bot = MockBot('', messages=0) + self._setup_updater('', messages=0) self.updater.dispatcher.addStringCommandHandler( 'test5', self.additionalArgsTest) @@ -354,8 +350,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_regexGroupHandler(self): print('Testing optional groups and groupdict parameters') - bot = MockBot('', messages=0) - self.updater.bot = bot + self._setup_updater('', messages=0) d = self.updater.dispatcher d.addStringRegexHandler('^(This).*?(?Pregex group).*', self.regexGroupHandlerTest) @@ -368,8 +363,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_runAsyncWithAdditionalArgs(self): print('Testing @run_async with additional parameters') - bot = MockBot('Test6', messages=2) - self.updater.bot = bot + self._setup_updater('Test6', messages=2) d = self.updater.dispatcher d.addTelegramMessageHandler( self.asyncAdditionalHandlerTest) @@ -380,8 +374,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_webhook(self): print('Testing Webhook') - bot = MockBot('', messages=0) - self.updater.bot = bot + self._setup_updater('', messages=0) d = self.updater.dispatcher d.addTelegramMessageHandler( self.telegramHandlerTest) @@ -442,8 +435,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_webhook_no_ssl(self): print('Testing Webhook without SSL') - bot = MockBot('', messages=0) - self.updater.bot = bot + self._setup_updater('', messages=0) d = self.updater.dispatcher d.addTelegramMessageHandler( self.telegramHandlerTest) @@ -485,7 +477,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_idle(self): print('Testing idle') - self.updater.bot = MockBot('Test6', messages=0) + self._setup_updater('Test6', messages=0) self.updater.start_polling(poll_interval=0.01) Thread(target=self.signalsender).start() self.updater.idle() @@ -493,6 +485,17 @@ class UpdaterTest(BaseTest, unittest.TestCase): sleep(1) self.updater.running = False + def test_createBot(self): + updater = Updater('123:abcd') + self.assertIsNotNone(updater.bot) + + def test_mutualExclusiveTokenBot(self): + bot = Bot('123:zyxw') + self.assertRaises(ValueError, Updater, token='123:abcd', bot=bot) + + def test_noTokenOrBot(self): + self.assertRaises(ValueError, Updater) + class MockBot: