merge master into inlinebots

This commit is contained in:
Jannes Hoeke 2016-01-25 18:05:27 +01:00
commit a383cee558
9 changed files with 255 additions and 35 deletions

View file

@ -16,10 +16,12 @@ The following wonderful people contributed directly or indirectly to this projec
- `JASON0916 <https://github.com/JASON0916>`_
- `jh0ker <https://github.com/jh0ker>`_
- `JRoot3D <https://github.com/JRoot3D>`_
- `jlmadurga <https://github.com/jlmadurga>`_
- `macrojames <https://github.com/macrojames>`_
- `naveenvhegde <https://github.com/naveenvhegde>`_
- `njittam <https://github.com/njittam>`_
- `Noam Meltzer <https://github.com/tsnoam>`_
- `Oleg Shlyazhko <https://github.com/ollmer>`_
- `Rahiel Kasim <https://github.com/rahiel>`_
- `sooyhwang <https://github.com/sooyhwang>`_
- `wjt <https://github.com/wjt>`_

View file

@ -51,8 +51,10 @@ from .update import Update
from .bot import Bot
from .dispatcher import Dispatcher
from .jobqueue import JobQueue
from .updatequeue import UpdateQueue
from .updater import Updater
__author__ = 'devs@python-telegram-bot.org'
__version__ = '3.3b1'
__all__ = ('Bot', 'Updater', 'Dispatcher', 'Emoji', 'TelegramError',
@ -63,4 +65,5 @@ __all__ = ('Bot', 'Updater', 'Dispatcher', 'Emoji', 'TelegramError',
'User', 'TelegramObject', 'NullHandler', 'Voice', 'JobQueue',
'InlineQuery', 'ChosenInlineResult', 'InlineQueryResultArticle',
'InlineQueryResultGif', 'InlineQueryResultPhoto',
'InlineQueryResultMpeg4Gif', 'InlineQueryResultVideo')
'InlineQueryResultMpeg4Gif', 'InlineQueryResultVideo',
'UpdateQueue')

View file

@ -110,6 +110,13 @@ class Dispatcher:
whole query split on spaces.
For other updates, args will be None
In some cases handlers may need some context data to process the update. To
procedure just queue in update_queue.put(update, context=context) or
processUpdate(update,context=context).
context:
Extra data for handling updates.
For regex-based handlers, you can also request information about the match.
For all other handlers, these will be None
@ -123,7 +130,7 @@ class Dispatcher:
Args:
bot (telegram.Bot): The bot object that should be passed to the
handlers
update_queue (queue.Queue): The synchronized queue that will
update_queue (UpdateQueue): The synchronized queue that will
contain the updates.
"""
def __init__(self, bot, update_queue, workers=4):
@ -174,14 +181,15 @@ class Dispatcher:
try:
# Pop update from update queue.
# Blocks if no updates are available.
update = self.update_queue.get()
update, context = self.update_queue.get(context=True)
if type(update) is self._Stop:
self.running = False
break
self.processUpdate(update)
self.logger.debug('Processed Update: %s' % update)
self.processUpdate(update, context)
self.logger.debug('Processed Update: %s with context %s'
% (update, context))
# Dispatch any errors
except TelegramError as te:
@ -207,7 +215,7 @@ class Dispatcher:
while self.running:
sleep(0.1)
def processUpdate(self, update):
def processUpdate(self, update, context=None):
"""
Processes a single update.
@ -220,15 +228,15 @@ class Dispatcher:
# Custom type handlers
for t in self.type_handlers:
if isinstance(update, t):
self.dispatchType(update)
self.dispatchType(update, context)
handled = True
# string update
if type(update) is str and update.startswith('/'):
self.dispatchStringCommand(update)
self.dispatchStringCommand(update, context)
handled = True
elif type(update) is str:
self.dispatchRegex(update)
self.dispatchRegex(update, context)
handled = True
# An error happened while polling
@ -238,21 +246,21 @@ class Dispatcher:
# Telegram update (regex)
if isinstance(update, Update) and update.message is not None:
self.dispatchRegex(update)
self.dispatchRegex(update, context)
handled = True
# Telegram update (command)
if update.message.text.startswith('/'):
self.dispatchTelegramCommand(update)
self.dispatchTelegramCommand(update, context)
# Telegram update (message)
else:
self.dispatchTelegramMessage(update)
self.dispatchTelegramMessage(update, context)
handled = True
elif isinstance(update, Update) and \
(update.inline_query is not None or
update.chosen_inline_result is not None):
self.dispatchTelegramInline(update)
self.dispatchTelegramInline(update, context)
handled = True
# Update not recognized
if not handled:
@ -519,7 +527,7 @@ class Dispatcher:
and handler in self.type_handlers[the_type]:
self.type_handlers[the_type].remove(handler)
def dispatchTelegramCommand(self, update):
def dispatchTelegramCommand(self, update, context=None):
"""
Dispatches an update that contains a command.
@ -532,11 +540,13 @@ class Dispatcher:
command = update.message.text.split(' ')[0][1:].split('@')[0]
if command in self.telegram_command_handlers:
self.dispatchTo(self.telegram_command_handlers[command], update)
self.dispatchTo(self.telegram_command_handlers[command], update,
context=context)
else:
self.dispatchTo(self.unknown_telegram_command_handlers, update)
self.dispatchTo(self.unknown_telegram_command_handlers, update,
context=context)
def dispatchRegex(self, update):
def dispatchRegex(self, update, context=None):
"""
Dispatches an update to all string or telegram regex handlers that
match the string/message content.
@ -559,9 +569,10 @@ class Dispatcher:
self.call_handler(handler,
update,
groups=m.groups(),
groupdict=m.groupdict())
groupdict=m.groupdict(),
context=context)
def dispatchStringCommand(self, update):
def dispatchStringCommand(self, update, context=None):
"""
Dispatches a string-update that contains a command.
@ -572,11 +583,13 @@ class Dispatcher:
command = update.split(' ')[0][1:]
if command in self.string_command_handlers:
self.dispatchTo(self.string_command_handlers[command], update)
self.dispatchTo(self.string_command_handlers[command], update,
context=context)
else:
self.dispatchTo(self.unknown_string_command_handlers, update)
self.dispatchTo(self.unknown_string_command_handlers, update,
context=context)
def dispatchType(self, update):
def dispatchType(self, update, context=None):
"""
Dispatches an update of any type.
@ -586,9 +599,9 @@ class Dispatcher:
for t in self.type_handlers:
if isinstance(update, t):
self.dispatchTo(self.type_handlers[t], update)
self.dispatchTo(self.type_handlers[t], update, context=context)
def dispatchTelegramMessage(self, update):
def dispatchTelegramMessage(self, update, context=None):
"""
Dispatches an update that contains a regular message.
@ -597,9 +610,10 @@ class Dispatcher:
message.
"""
self.dispatchTo(self.telegram_message_handlers, update)
self.dispatchTo(self.telegram_message_handlers, update,
context=context)
def dispatchTelegramInline(self, update):
def dispatchTelegramInline(self, update, context=None):
"""
Dispatches an update that contains an inline update.
@ -608,7 +622,7 @@ class Dispatcher:
message.
"""
self.dispatchTo(self.telegram_inline_handlers, update)
self.dispatchTo(self.telegram_inline_handlers, update, context=None)
def dispatchError(self, update, error):
"""
@ -675,4 +689,7 @@ class Dispatcher:
if is_async or 'groupdict' in fargs:
target_kwargs['groupdict'] = kwargs.get('groupdict', None)
if is_async or 'context' in fargs:
target_kwargs['context'] = kwargs.get('context', None)
handler(self.bot, update, **target_kwargs)

59
telegram/updatequeue.py Normal file
View file

@ -0,0 +1,59 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the class UpdateQueue to override standard
Queue."""
# Adjust for differences in Python versions
try:
from queue import Queue
except ImportError:
from Queue import Queue
class UpdateQueue(Queue):
"""
This class overrides standard Queues. Allows you to de/queue context
data apart from the handled `update`
"""
def put(self, item, block=True, timeout=None, context=None):
"""
Put an item into the queue with context data if provided as a
tuple (item, context). Overrides standard Queue.put method.
Args:
update (any): handled by the dispatcher
context (any): extra data to use in handlers
"""
Queue.put(self, (item, context), block, timeout)
def get(self, block=True, timeout=None, context=False):
"""
Remove and return an item from the queue. A tuple of
(update, context) if requested. Overrides standard Queue.get
method.
Args:
context (boolean): set true to get (update, context)
"""
if not context:
return Queue.get(self, block, timeout)[0]
return Queue.get(self, block, timeout)

View file

@ -29,15 +29,9 @@ from time import sleep
import subprocess
from signal import signal, SIGINT, SIGTERM, SIGABRT
from telegram import (Bot, TelegramError, dispatcher, Dispatcher,
NullHandler, JobQueue)
NullHandler, JobQueue, UpdateQueue)
from telegram.utils.webhookhandler import (WebhookServer, WebhookHandler)
# Adjust for differences in Python versions
try:
from Queue import Queue
except ImportError:
from queue import Queue
try:
from urllib2 import URLError
except ImportError:
@ -89,7 +83,7 @@ class Updater:
self.bot = bot
else:
self.bot = Bot(token, base_url)
self.update_queue = Queue()
self.update_queue = UpdateQueue()
self.job_queue = JobQueue(self.bot, job_queue_tick_interval)
self.dispatcher = Dispatcher(self.bot, self.update_queue,
workers=workers)

55
telegram/utils/botan.py Normal file
View file

@ -0,0 +1,55 @@
#!/usr/bin/env python
import json
import logging
from telegram import NullHandler
try:
from urllib.request import urlopen, Request
from urllib.parse import quote
from urllib.error import URLError, HTTPError
except ImportError:
from urllib2 import urlopen, Request
from urllib import quote
from urllib2 import URLError, HTTPError
H = NullHandler()
logging.getLogger(__name__).addHandler(H)
class Botan(object):
"""This class helps to send incoming events in your botan analytics account.
See more: https://github.com/botanio/sdk#botan-sdk"""
token = ''
url_template = 'https://api.botan.io/track?token={token}' \
'&uid={uid}&name={name}&src=python-telegram-bot'
def __init__(self, token):
self.token = token
self.logger = logging.getLogger(__name__)
def track(self, message, event_name='event'):
try:
uid = message.chat_id
except AttributeError:
self.logger.warn('No chat_id in message')
return False
data = json.dumps(message.__dict__)
try:
url = self.url_template.format(token=str(self.token),
uid=str(uid),
name=quote(event_name))
request = Request(url,
data=data.encode(),
headers={'Content-Type': 'application/json'})
urlopen(request)
return True
except HTTPError as error:
self.logger.warn('Botan track error ' +
str(error.code) +
':' + error.read().decode('utf-8'))
return False
except URLError as error:
self.logger.warn('Botan track error ' + str(error.reason))
return False

View file

@ -25,6 +25,13 @@ import json
import socket
from ssl import SSLError
try:
# python2
from httplib import HTTPException
except ImportError:
# python3
from http.client import HTTPException
try:
from urllib.request import urlopen, urlretrieve, Request
from urllib.error import HTTPError
@ -82,6 +89,8 @@ def _try_except_req(func):
raise TelegramError("Timed out")
raise TelegramError(str(error))
except HTTPException as error:
raise TelegramError('HTTPException: {0!r}'.format(error))
return decorator

62
tests/test_botan.py Normal file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env python
"""This module contains a object that represents Tests for Botan analytics integration"""
import os
import unittest
import sys
sys.path.append('.')
from telegram.utils.botan import Botan
from tests.base import BaseTest
class MessageMock(object):
chat_id = None
def __init__(self, chat_id):
self.chat_id = chat_id
class BotanTest(BaseTest, unittest.TestCase):
"""This object represents Tests for Botan analytics integration."""
token = os.environ.get('BOTAN_TOKEN')
def test_track(self):
"""Test sending event to botan"""
print('Test sending event to botan')
botan = Botan(self.token)
message = MessageMock(self._chat_id)
result = botan.track(message, 'named event')
self.assertTrue(result)
def test_track_fail(self):
"""Test fail when sending event to botan"""
print('Test fail when sending event to botan')
botan = Botan(self.token)
botan.url_template = 'https://api.botan.io/traccc?token={token}&uid={uid}&name={name}'
message = MessageMock(self._chat_id)
result = botan.track(message, 'named event')
self.assertFalse(result)
def test_wrong_message(self):
"""Test sending wrong message"""
print('Test sending wrong message')
botan = Botan(self.token)
message = MessageMock(self._chat_id)
message = delattr(message, 'chat_id')
result = botan.track(message, 'named event')
self.assertFalse(result)
def test_wrong_endpoint(self):
"""Test wrong endpoint"""
print('Test wrong endpoint')
botan = Botan(self.token)
botan.url_template = 'https://api.botaaaaan.io/traccc?token={token}&uid={uid}&name={name}'
message = MessageMock(self._chat_id)
result = botan.track(message, 'named event')
self.assertFalse(result)
if __name__ == '__main__':
unittest.main()

View file

@ -109,6 +109,11 @@ class UpdaterTest(BaseTest, unittest.TestCase):
update_queue.put('/test5 noresend')
elif args[0] == 'noresend':
pass
def contextTest(self, bot, update, context):
self.received_message = update
self.message_count += 1
self.context = context
@run_async
def asyncAdditionalHandlerTest(self, bot, update, update_queue=None,
@ -348,6 +353,20 @@ class UpdaterTest(BaseTest, unittest.TestCase):
sleep(.1)
self.assertEqual(self.received_message, '/test5 noresend')
self.assertEqual(self.message_count, 2)
def test_context(self):
print('Testing context for handlers')
context = "context_data"
self._setup_updater('', messages=0)
self.updater.dispatcher.addStringCommandHandler(
'test_context', self.contextTest)
queue = self.updater.start_polling(0.01)
queue.put('/test_context', context=context)
sleep(.5)
self.assertEqual(self.received_message, '/test_context')
self.assertEqual(self.message_count, 1)
self.assertEqual(self.context, context)
def test_regexGroupHandler(self):
print('Testing optional groups and groupdict parameters')