1
0
mirror of https://github.com/eternnoir/pyTelegramBotAPI.git synced 2023-08-10 21:12:57 +03:00

Merge pull request #22 from pevdh/develop

Added AsyncTeleBot
This commit is contained in:
FrankWang 2015-07-03 08:15:17 +08:00
commit 288bb95553
5 changed files with 307 additions and 84 deletions

126
README.md
View File

@ -1,12 +1,13 @@
# pyTelegramBotAPI # pyTelegramBotAPI
Python Telegram Bot API. A Python implementation for the Telegram Bot API.
[https://core.telegram.org/bots/api](https://core.telegram.org/bots/api) See [https://core.telegram.org/bots/api](https://core.telegram.org/bots/api)
## How to install ## How to install
* Need python2 or python3 Python 2 or Python 3 is required.
* Install from source * Install from source
``` ```
@ -15,7 +16,7 @@ $ cd pyTelegramBotAPI
$ python setup.py install $ python setup.py install
``` ```
* or install by pip * or install with pip
``` ```
$ pip install pyTelegramBotAPI $ pip install pyTelegramBotAPI
@ -23,7 +24,7 @@ $ pip install pyTelegramBotAPI
## Example ## Example
* Send Message * Sending a message.
```python ```python
import telebot import telebot
@ -31,8 +32,8 @@ import telebot
TOKEN = '<token string>' TOKEN = '<token string>'
tb = telebot.TeleBot(TOKEN) tb = telebot.TeleBot(TOKEN)
# tb.send_message(chatid,message) # tb.send_message(chatid, message)
print tb.send_message(281281, 'gogo power ranger') tb.send_message(281281, 'gogo power ranger')
``` ```
* Echo Bot * Echo Bot
@ -45,23 +46,21 @@ TOKEN = '<token_string>'
def listener(*messages): def listener(*messages):
""" """
When new message get will call this function. When new messages arrive TeleBot will call this function.
:param messages:
:return:
""" """
for m in messages: for m in messages:
chatid = m.chat.id chatid = m.chat.id
if m.content_type == 'text' if m.content_type == 'text':
text = m.text text = m.text
tb.send_message(chatid, text) tb.send_message(chatid, text)
tb = telebot.TeleBot(TOKEN) tb = telebot.TeleBot(TOKEN)
tb.get_update() # cache exist message
tb.set_update_listener(listener) #register listener tb.set_update_listener(listener) #register listener
tb.polling(3) tb.polling()
while True:
time.sleep(20) while True: # Don't let the main Thread end.
pass
``` ```
## TeleBot API usage ## TeleBot API usage
@ -71,7 +70,7 @@ import telebot
import time import time
TOKEN = '<token_string>' TOKEN = '<token_string>'
tb = telebot.TeleBot(TOKEN) #create new Telegram Bot object tb = telebot.TeleBot(TOKEN) #create a new Telegram Bot object
# getMe # getMe
user = tb.get_me() user = tb.get_me()
@ -107,43 +106,59 @@ tb.send_video(chat_id, video)
tb.send_location(chat_id, lat, lon) tb.send_location(chat_id, lat, lon)
# sendChatAction # sendChatAction
# action_string can be : typing,upload_photo,record_video,upload_video,record_audio,upload_audio,upload_document, # action_string can be one of the following strings: 'typing', 'upload_photo', 'record_video', 'upload_video',
# find_location. # 'record_audio', 'upload_audio', 'upload_document' or 'find_location'.
tb.send_chat_action(chat_id, action_string) tb.send_chat_action(chat_id, action_string)
# ReplyKeyboardMarkup. # Use the ReplyKeyboardMarkup class.
# Use ReplyKeyboardMarkup class.
# Thanks pevdh. # Thanks pevdh.
from telebot import types from telebot import types
markup = types.ReplyKeyboardMarkup() markup = types.ReplyKeyboardMarkup()
markup.add('a', 'v', 'd') markup.add('a', 'v', 'd')
tb.send_message(chat_id, message, None, None, markup) tb.send_message(chat_id, message, reply_markup=markup)
# or use row method
# or add strings one row at a time:
markup = types.ReplyKeyboardMarkup() markup = types.ReplyKeyboardMarkup()
markup.row('a', 'v') markup.row('a', 'v')
markup.row('c', 'd', 'e') markup.row('c', 'd', 'e')
tb.send_message(chat_id, message, None, None, markup) tb.send_message(chat_id, message, reply_markup=markup)
``` ```
## Message notifier ## Creating a Telegram bot with the pyTelegramBotAPI
There are two ways to define a Telegram Bot with the pyTelegramBotAPI.
* Define listener function ### The listener mechanism
* First, create a TeleBot instance.
```python ```python
def listener1(*messages): import telebot
TOKEN = '<token string>'
bot = telebot.TeleBot(TOKEN)
```
* Then, define a listener function.
```python
def echo_messages(*messages):
"""
Echoes all incoming messages of content_type 'text'.
"""
for m in messages: for m in messages:
chatid = m.chat.id chatid = m.chat.id
if m.content_type == 'text' if m.content_type == 'text':
text = m.text text = m.text
tb.send_message(chatid, text) bot.send_message(chatid, text)
``` ```
* Now, register your listener with the TeleBot instance and call TeleBot#polling()
```python
bot.set_update_listener(echo_messages)
bot.polling()
* Use ***set_update_listener*** method to add listener function to telebot. while True: # Don't let the main Thread end.
* Start polling or call get_update(). If get new updates, telebot will call listener and pass messages to listener. pass
* use Message's content_type attribute to check message type. Now Message support content_type: ```
* use Message's content_type attribute to check the type of Message. Now Message supports content types:
* text * text
* audio * audio
* document * document
@ -151,6 +166,49 @@ def listener1(*messages):
* sticker * sticker
* video * video
* location * location
* That's it!
### The decorator mechanism
* First, create a TeleBot instance.
```python
import telebot
TOKEN = '<token string>'
bot = telebot.TeleBot(TOKEN)
```
* Next, define all of your so-called message handlers and decorate them with @bot.message_handler
```python
# Handle /start and /help
@bot.message_handler(commands=['start', 'help'])
def command_help(message):
bot.reply_to(message, "Hello, did someone call for help?")
# Handles all messages which text matches the regex regexp.
# See https://en.wikipedia.org/wiki/Regular_expression
# This regex matches all sent url's.
@bot.message_handler(regexp='((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!)?)*)')
def command_url(message):
bot.reply_to(message, "I shouldn't open that url, should I?")
# Handle all sent documents of type 'text/plain'.
@bot.message_handler(func=lambda message: message.document.mime_type == 'text/plain', content_types=['document'])
def command_handle_document(message):
bot.reply_to(message, "Document received, sir!")
# Default command handler. A lambda expression which always returns True is used for this purpose.
@bot.message_handler(func=lambda message: True, content_types=['audio', 'video', 'document', 'text', 'location', 'contact', 'sticker'])
def default_command(message):
bot.reply_to(message, "This is the default command handler.")
```
* And finally, call bot.polling()
```python
bot.polling()
while True: # Don't end the main thread.
pass
```
Use whichever mechanism fits your purpose! It is even possible to mix and match.
## TODO ## TODO
@ -165,4 +223,4 @@ def listener1(*messages):
- [x] sendLocation - [x] sendLocation
- [x] sendChatAction - [x] sendChatAction
- [x] getUserProfilePhotos - [x] getUserProfilePhotos
- [ ] getUpdat(chat message not yet) - [ ] getUpdate(chat message not yet)

28
examples/echo_bot.py Normal file
View File

@ -0,0 +1,28 @@
# This is a simple echo bot using the decorator mechanism.
# It echoes any incoming text messages.
import telebot
API_TOKEN = '<api_token>'
bot = telebot.TeleBot(API_TOKEN)
# Handle '/start' and '/help'
@bot.message_handler(commands=['help, start'])
def send_welcome(message):
bot.reply_to(message, """\
Hi there, I am EchoBot.
I am here to echo your kind words back to you. Just say anything nice and I'll say the exact same thing to you!\
""")
# Handle all other messages with content_type 'text' (content_types defaults to ['text'])
@bot.message_handler(func=lambda message: True)
def echo_message(message):
bot.reply_to(message, message.text)
bot.polling()
while True:
pass

View File

@ -41,7 +41,7 @@ class TeleBot:
self.last_update_id = 0 self.last_update_id = 0
self.commands = [] self.message_handlers = []
def get_update(self): def get_update(self):
""" """
@ -49,7 +49,7 @@ class TeleBot:
Registered listeners and applicable message handlers will be notified when a new message arrives. Registered listeners and applicable message handlers will be notified when a new message arrives.
:raises ApiException when a call has failed. :raises ApiException when a call has failed.
""" """
updates = apihelper.get_updates(self.token, offset=(self.last_update_id + 1)) updates = apihelper.get_updates(self.token, offset=(self.last_update_id + 1), timeout=20)
new_messages = [] new_messages = []
for update in updates: for update in updates:
if update['update_id'] > self.last_update_id: if update['update_id'] > self.last_update_id:
@ -66,31 +66,32 @@ class TeleBot:
t = threading.Thread(target=listener, args=new_messages) t = threading.Thread(target=listener, args=new_messages)
t.start() t.start()
def polling(self, interval=3): def polling(self):
""" """
This function creates a new Thread that calls an internal __polling function.
This allows the bot to retrieve Updates automagically and notify listeners and message handlers accordingly.
Do not call this function more than once!
Always get updates. Always get updates.
:param interval: interval secs.
:return: :return:
""" """
self.interval = interval
# clear thread.
self.__stop_polling = True
time.sleep(interval + 1)
self.__stop_polling = False self.__stop_polling = False
self.polling_thread = threading.Thread(target=self.__polling, args=()) self.polling_thread = threading.Thread(target=self.__polling, args=())
self.polling_thread.daemon = True self.polling_thread.daemon = True
self.polling_thread.start() self.polling_thread.start()
def __polling(self): def __polling(self):
print('telegram bot start polling') print('TeleBot: Started polling.')
while not self.__stop_polling: while not self.__stop_polling:
try: try:
self.get_update() self.get_update()
except Exception as e: except Exception as e:
print("TeleBot: Exception occurred. Stopping.")
self.__stop_polling = True
print(e) print(e)
time.sleep(self.interval)
print('telegram bot stop polling') print('TeleBot: Stopped polling.')
def stop_polling(self): def stop_polling(self):
self.__stop_polling = True self.__stop_polling = True
@ -109,7 +110,7 @@ class TeleBot:
:param user_id: :param user_id:
:param offset: :param offset:
:param limit: :param limit:
:return: :return: API reply.
""" """
result = apihelper.get_user_profile_photos(self.token, user_id, offset, limit) result = apihelper.get_user_profile_photos(self.token, user_id, offset, limit)
return types.UserProfilePhotos.de_json(result) return types.UserProfilePhotos.de_json(result)
@ -122,10 +123,11 @@ class TeleBot:
:param disable_web_page_preview: :param disable_web_page_preview:
:param reply_to_message_id: :param reply_to_message_id:
:param reply_markup: :param reply_markup:
:return: :return: API reply.
""" """
return apihelper.send_message(self.token, chat_id, text, disable_web_page_preview, reply_to_message_id, return types.Message.de_json(
reply_markup) apihelper.send_message(self.token, chat_id, text, disable_web_page_preview, reply_to_message_id,
reply_markup))
def forward_message(self, chat_id, from_chat_id, message_id): def forward_message(self, chat_id, from_chat_id, message_id):
""" """
@ -133,9 +135,9 @@ class TeleBot:
:param chat_id: which chat to forward :param chat_id: which chat to forward
:param from_chat_id: which chat message from :param from_chat_id: which chat message from
:param message_id: message id :param message_id: message id
:return: :return: API reply.
""" """
return apihelper.forward_message(self.token, chat_id, from_chat_id, message_id) return types.Message.de_json(apihelper.forward_message(self.token, chat_id, from_chat_id, message_id))
def send_photo(self, chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None): def send_photo(self, chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None):
""" """
@ -145,9 +147,10 @@ class TeleBot:
:param caption: :param caption:
:param reply_to_message_id: :param reply_to_message_id:
:param reply_markup: :param reply_markup:
:return: :return: API reply.
""" """
return apihelper.send_photo(self.token, chat_id, photo, caption, reply_to_message_id, reply_markup) return types.Message.de_json(
apihelper.send_photo(self.token, chat_id, photo, caption, reply_to_message_id, reply_markup))
def send_audio(self, chat_id, data, reply_to_message_id=None, reply_markup=None): def send_audio(self, chat_id, data, reply_to_message_id=None, reply_markup=None):
""" """
@ -157,9 +160,10 @@ class TeleBot:
:param data: :param data:
:param reply_to_message_id: :param reply_to_message_id:
:param reply_markup: :param reply_markup:
:return: :return: API reply.
""" """
return apihelper.send_data(self.token, chat_id, data, 'audio', reply_to_message_id, reply_markup) return types.Message.de_json(
apihelper.send_data(self.token, chat_id, data, 'audio', reply_to_message_id, reply_markup))
def send_document(self, chat_id, data, reply_to_message_id=None, reply_markup=None): def send_document(self, chat_id, data, reply_to_message_id=None, reply_markup=None):
""" """
@ -168,9 +172,10 @@ class TeleBot:
:param data: :param data:
:param reply_to_message_id: :param reply_to_message_id:
:param reply_markup: :param reply_markup:
:return: :return: API reply.
""" """
return apihelper.send_data(self.token, chat_id, data, 'document', reply_to_message_id, reply_markup) return types.Message.de_json(
apihelper.send_data(self.token, chat_id, data, 'document', reply_to_message_id, reply_markup))
def send_sticker(self, chat_id, data, reply_to_message_id=None, reply_markup=None): def send_sticker(self, chat_id, data, reply_to_message_id=None, reply_markup=None):
""" """
@ -179,9 +184,10 @@ class TeleBot:
:param data: :param data:
:param reply_to_message_id: :param reply_to_message_id:
:param reply_markup: :param reply_markup:
:return: :return: API reply.
""" """
return apihelper.send_data(self.token, chat_id, data, 'sticker', reply_to_message_id, reply_markup) return types.Message.de_json(
apihelper.send_data(self.token, chat_id, data, 'sticker', reply_to_message_id, reply_markup))
def send_video(self, chat_id, data, reply_to_message_id=None, reply_markup=None): def send_video(self, chat_id, data, reply_to_message_id=None, reply_markup=None):
""" """
@ -190,9 +196,10 @@ class TeleBot:
:param data: :param data:
:param reply_to_message_id: :param reply_to_message_id:
:param reply_markup: :param reply_markup:
:return: :return: API reply.
""" """
return apihelper.send_data(self.token, chat_id, data, 'video', reply_to_message_id, reply_markup) return types.Message.de_json(
apihelper.send_data(self.token, chat_id, data, 'video', reply_to_message_id, reply_markup))
def send_location(self, chat_id, latitude, longitude, reply_to_message_id=None, reply_markup=None): def send_location(self, chat_id, latitude, longitude, reply_to_message_id=None, reply_markup=None):
""" """
@ -202,9 +209,10 @@ class TeleBot:
:param longitude: :param longitude:
:param reply_to_message_id: :param reply_to_message_id:
:param reply_markup: :param reply_markup:
:return: :return: API reply.
""" """
return apihelper.send_location(self.token, chat_id, latitude, longitude, reply_to_message_id, reply_markup) return types.Message.de_json(
apihelper.send_location(self.token, chat_id, latitude, longitude, reply_to_message_id, reply_markup))
def send_chat_action(self, chat_id, action): def send_chat_action(self, chat_id, action):
""" """
@ -212,13 +220,16 @@ class TeleBot:
The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear
its typing status). its typing status).
:param chat_id: :param chat_id:
:param action: string . typing,upload_photo,record_video,upload_video,record_audio,upload_audio,upload_document, :param action: One of the following strings: 'typing', 'upload_photo', 'record_video', 'upload_video',
find_location. 'record_audio', 'upload_audio', 'upload_document', 'find_location'.
:return: :return: API reply.
""" """
return apihelper.send_chat_action(self.token, chat_id, action) return types.Message.de_json(apihelper.send_chat_action(self.token, chat_id, action))
def message_handler(self, regexp=None, func=None, content_types=['text']): def reply_to(self, message, text, **kwargs):
return self.send_message(message.chat.id, text, reply_to_message_id=message.message_id, **kwargs)
def message_handler(self, commands=None, regexp=None, func=None, content_types=['text']):
""" """
Message handler decorator. Message handler decorator.
This decorator can be used to decorate functions that must handle certain types of messages. This decorator can be used to decorate functions that must handle certain types of messages.
@ -246,27 +257,148 @@ class TeleBot:
:param regexp: Optional regular expression. :param regexp: Optional regular expression.
:param func: Optional lambda function. The lambda receives the message to test as the first parameter. It must return True if the command should handle the message. :param func: Optional lambda function. The lambda receives the message to test as the first parameter. It must return True if the command should handle the message.
:param content_types: This commands' supported content types. Must be a list. Defaults to ['text']. :param content_types: This commands' supported content types. Must be a list. Defaults to ['text'].
:return:
""" """
def decorator(fn): def decorator(fn):
self.commands.append([fn, regexp, func, content_types]) func_dict = {'function': fn, 'content_types': content_types}
if regexp:
func_dict['regexp'] = regexp if 'text' in content_types else None
if func:
func_dict['lambda'] = func
if commands:
func_dict['commands'] = commands if 'text' in content_types else None
self.message_handlers.append(func_dict)
return fn return fn
return decorator return decorator
@staticmethod @staticmethod
def _test_command(command, message): def is_command(text):
if message.content_type not in command[3]: """
Checks if `text` is a command. Telegram chat commands start with the '/' character.
:param text: Text to check.
:return: True if `text` is a command, else False.
"""
return text.startswith('/')
@staticmethod
def extract_command(text):
"""
Extracts the command from `text` (minus the '/') if `text` is a command (see is_command).
If `text` is not a command, this function returns None.
Examples:
extract_command('/help'): 'help'
extract_command('/search black eyed peas'): 'search'
extract_command('Good day to you'): None
:param text: String to extract the command from
:return: the command if `text` is a command, else None.
"""
return text.split()[0][1:] if TeleBot.is_command(text) else None
@staticmethod
def _test_message_handler(message_handler, message):
if message.content_type not in message_handler['content_types']:
return False return False
if command[1] is not None and message.content_type == 'text' and re.search(command[1], message.text): if 'commands' in message_handler and message.content_type == 'text':
return True return TeleBot.extract_command(message.text) in message_handler['commands']
if command[2] is not None: if 'regexp' in message_handler and message.content_type == 'text' and re.search(message_handler['regexp'],
return command[2](message) message.text):
return False
if 'lambda' in message_handler:
return message_handler['lambda'](message)
return False return False
def _notify_command_handlers(self, new_messages): def _notify_command_handlers(self, new_messages):
for message in new_messages: for message in new_messages:
for command in self.commands: for message_handler in self.message_handlers:
if self._test_command(command, message): if self._test_message_handler(message_handler, message):
t = threading.Thread(target=command[0], args=(message,)) t = threading.Thread(target=message_handler['function'], args=(message,))
t.start() t.start()
break break
class AsyncTask:
def __init__(self, target, *args, **kwargs):
self.target = target
self.args = args
self.kwargs = kwargs
self.done = False
self.thread = threading.Thread(target=self._run)
self.thread.start()
def _run(self):
try:
self.result = self.target(*self.args, **self.kwargs)
except Exception as e:
self.result = e
self.done = True
def wait(self):
if not self.done:
self.thread.join()
if isinstance(self.result, Exception):
raise self.result
else:
return self.result
def async():
def decorator(fn):
def wrapper(*args, **kwargs):
return AsyncTask(fn, *args, **kwargs)
return wrapper
return decorator
class AsyncTeleBot(TeleBot):
def __init__(self, *args, **kwargs):
TeleBot.__init__(self, *args, **kwargs)
@async()
def get_me(self):
return TeleBot.get_me(self)
@async()
def get_user_profile_photos(self, *args, **kwargs):
return TeleBot.get_user_profile_photos(self, *args, **kwargs)
@async()
def send_message(self, *args, **kwargs):
return TeleBot.send_message(self, *args, **kwargs)
@async()
def forward_message(self, *args, **kwargs):
return TeleBot.forward_message(self, *args, **kwargs)
@async()
def send_photo(self, *args, **kwargs):
return TeleBot.send_photo(self, *args, **kwargs)
@async()
def send_audio(self, *args, **kwargs):
return TeleBot.send_audio(self, *args, **kwargs)
@async()
def send_document(self, *args, **kwargs):
return TeleBot.send_document(self, *args, **kwargs)
@async()
def send_sticker(self, *args, **kwargs):
return TeleBot.send_sticker(self, *args, **kwargs)
@async()
def send_video(self, *args, **kwargs):
return TeleBot.send_video(self, *args, **kwargs)
@async()
def send_location(self, *args, **kwargs):
return TeleBot.send_location(self, *args, **kwargs)
@async()
def send_chat_action(self, *args, **kwargs):
return TeleBot.send_chat_action(self, *args, **kwargs)

View File

@ -56,12 +56,16 @@ def send_message(token, chat_id, text, disable_web_page_preview=None, reply_to_m
return _make_request(token, method_url, params=payload) return _make_request(token, method_url, params=payload)
def get_updates(token, offset=None): def get_updates(token, offset=None, limit=None, timeout=None):
method_url = r'getUpdates' method_url = r'getUpdates'
if offset is not None: payload = {}
return _make_request(token, method_url, params={'offset': offset}) if offset:
else: payload['offset'] = offset
return _make_request(token, method_url) if limit:
payload['limit'] = limit
if timeout:
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload)
def get_user_profile_photos(token, user_id, offset=None, limit=None): def get_user_profile_photos(token, user_id, offset=None, limit=None):

View File

@ -290,6 +290,7 @@ class Contact(JsonDeserializable):
phone_number = obj['phone_number'] phone_number = obj['phone_number']
first_name = obj['first_name'] first_name = obj['first_name']
last_name = None last_name = None
user_id = None
if 'last_name' in obj: if 'last_name' in obj:
last_name = obj['last_name'] last_name = obj['last_name']
if 'user_id' in obj: if 'user_id' in obj: