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
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
* Need python2 or python3
Python 2 or Python 3 is required.
* Install from source
```
@ -15,7 +16,7 @@ $ cd pyTelegramBotAPI
$ python setup.py install
```
* or install by pip
* or install with pip
```
$ pip install pyTelegramBotAPI
@ -23,7 +24,7 @@ $ pip install pyTelegramBotAPI
## Example
* Send Message
* Sending a message.
```python
import telebot
@ -31,8 +32,8 @@ import telebot
TOKEN = '<token string>'
tb = telebot.TeleBot(TOKEN)
# tb.send_message(chatid,message)
print tb.send_message(281281, 'gogo power ranger')
# tb.send_message(chatid, message)
tb.send_message(281281, 'gogo power ranger')
```
* Echo Bot
@ -45,23 +46,21 @@ TOKEN = '<token_string>'
def listener(*messages):
"""
When new message get will call this function.
:param messages:
:return:
When new messages arrive TeleBot will call this function.
"""
for m in messages:
chatid = m.chat.id
if m.content_type == 'text'
if m.content_type == 'text':
text = m.text
tb.send_message(chatid, text)
tb = telebot.TeleBot(TOKEN)
tb.get_update() # cache exist message
tb.set_update_listener(listener) #register listener
tb.polling(3)
while True:
time.sleep(20)
tb.polling()
while True: # Don't let the main Thread end.
pass
```
## TeleBot API usage
@ -71,7 +70,7 @@ import telebot
import time
TOKEN = '<token_string>'
tb = telebot.TeleBot(TOKEN) #create new Telegram Bot object
tb = telebot.TeleBot(TOKEN) #create a new Telegram Bot object
# getMe
user = tb.get_me()
@ -107,43 +106,59 @@ tb.send_video(chat_id, video)
tb.send_location(chat_id, lat, lon)
# sendChatAction
# action_string can be : typing,upload_photo,record_video,upload_video,record_audio,upload_audio,upload_document,
# find_location.
# action_string can be one of the following strings: 'typing', 'upload_photo', 'record_video', 'upload_video',
# 'record_audio', 'upload_audio', 'upload_document' or 'find_location'.
tb.send_chat_action(chat_id, action_string)
# ReplyKeyboardMarkup.
# Use ReplyKeyboardMarkup class.
# Use the ReplyKeyboardMarkup class.
# Thanks pevdh.
from telebot import types
markup = types.ReplyKeyboardMarkup()
markup.add('a', 'v', 'd')
tb.send_message(chat_id, message, None, None, markup)
# or use row method
tb.send_message(chat_id, message, reply_markup=markup)
# or add strings one row at a time:
markup = types.ReplyKeyboardMarkup()
markup.row('a', 'v')
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
* Define listener function
## Creating a Telegram bot with the pyTelegramBotAPI
There are two ways to define a Telegram Bot with the pyTelegramBotAPI.
### The listener mechanism
* First, create a TeleBot instance.
```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:
chatid = m.chat.id
if m.content_type == 'text'
if m.content_type == '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.
* Start polling or call get_update(). If get new updates, telebot will call listener and pass messages to listener.
* use Message's content_type attribute to check message type. Now Message support content_type:
while True: # Don't let the main Thread end.
pass
```
* use Message's content_type attribute to check the type of Message. Now Message supports content types:
* text
* audio
* document
@ -151,6 +166,49 @@ def listener1(*messages):
* sticker
* video
* 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
@ -165,4 +223,4 @@ def listener1(*messages):
- [x] sendLocation
- [x] sendChatAction
- [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.commands = []
self.message_handlers = []
def get_update(self):
"""
@ -49,7 +49,7 @@ class TeleBot:
Registered listeners and applicable message handlers will be notified when a new message arrives.
: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 = []
for update in updates:
if update['update_id'] > self.last_update_id:
@ -66,31 +66,32 @@ class TeleBot:
t = threading.Thread(target=listener, args=new_messages)
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.
:param interval: interval secs.
:return:
"""
self.interval = interval
# clear thread.
self.__stop_polling = True
time.sleep(interval + 1)
self.__stop_polling = False
self.polling_thread = threading.Thread(target=self.__polling, args=())
self.polling_thread.daemon = True
self.polling_thread.start()
def __polling(self):
print('telegram bot start polling')
print('TeleBot: Started polling.')
while not self.__stop_polling:
try:
self.get_update()
except Exception as e:
print("TeleBot: Exception occurred. Stopping.")
self.__stop_polling = True
print(e)
time.sleep(self.interval)
print('telegram bot stop polling')
print('TeleBot: Stopped polling.')
def stop_polling(self):
self.__stop_polling = True
@ -109,7 +110,7 @@ class TeleBot:
:param user_id:
:param offset:
:param limit:
:return:
:return: API reply.
"""
result = apihelper.get_user_profile_photos(self.token, user_id, offset, limit)
return types.UserProfilePhotos.de_json(result)
@ -122,10 +123,11 @@ class TeleBot:
:param disable_web_page_preview:
:param reply_to_message_id:
:param reply_markup:
:return:
:return: API reply.
"""
return apihelper.send_message(self.token, chat_id, text, disable_web_page_preview, reply_to_message_id,
reply_markup)
return types.Message.de_json(
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):
"""
@ -133,9 +135,9 @@ class TeleBot:
:param chat_id: which chat to forward
:param from_chat_id: which chat message from
: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):
"""
@ -145,9 +147,10 @@ class TeleBot:
:param caption:
:param reply_to_message_id:
: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):
"""
@ -157,9 +160,10 @@ class TeleBot:
:param data:
:param reply_to_message_id:
: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):
"""
@ -168,9 +172,10 @@ class TeleBot:
:param data:
:param reply_to_message_id:
: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):
"""
@ -179,9 +184,10 @@ class TeleBot:
:param data:
:param reply_to_message_id:
: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):
"""
@ -190,9 +196,10 @@ class TeleBot:
:param data:
:param reply_to_message_id:
: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):
"""
@ -202,9 +209,10 @@ class TeleBot:
:param longitude:
:param reply_to_message_id:
: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):
"""
@ -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
its typing status).
:param chat_id:
:param action: string . typing,upload_photo,record_video,upload_video,record_audio,upload_audio,upload_document,
find_location.
:return:
:param action: One of the following strings: 'typing', 'upload_photo', 'record_video', 'upload_video',
'record_audio', 'upload_audio', 'upload_document', 'find_location'.
: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.
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 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'].
:return:
"""
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 decorator
@staticmethod
def _test_command(command, message):
if message.content_type not in command[3]:
def is_command(text):
"""
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
if command[1] is not None and message.content_type == 'text' and re.search(command[1], message.text):
return True
if command[2] is not None:
return command[2](message)
if 'commands' in message_handler and message.content_type == 'text':
return TeleBot.extract_command(message.text) in message_handler['commands']
if 'regexp' in message_handler and message.content_type == 'text' and re.search(message_handler['regexp'],
message.text):
return False
if 'lambda' in message_handler:
return message_handler['lambda'](message)
return False
def _notify_command_handlers(self, new_messages):
for message in new_messages:
for command in self.commands:
if self._test_command(command, message):
t = threading.Thread(target=command[0], args=(message,))
for message_handler in self.message_handlers:
if self._test_message_handler(message_handler, message):
t = threading.Thread(target=message_handler['function'], args=(message,))
t.start()
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)
def get_updates(token, offset=None):
def get_updates(token, offset=None, limit=None, timeout=None):
method_url = r'getUpdates'
if offset is not None:
return _make_request(token, method_url, params={'offset': offset})
else:
return _make_request(token, method_url)
payload = {}
if offset:
payload['offset'] = offset
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):

View File

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