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

Compare commits

..

47 Commits
0.3.6 ... 1.4.0

Author SHA1 Message Date
528b23770f Update README.md 2015-10-26 22:36:32 +08:00
4e472dda48 Update README.md 2015-10-26 22:32:10 +08:00
17dec34923 Update README.md 2015-10-26 22:31:55 +08:00
a17b301f1c Jump to release version. 2015-10-26 22:26:04 +08:00
cf830a0fb4 Version update:
Change log:
- Code clean up
- Bugs fix
2015-10-26 22:15:03 +08:00
7e9f5b09cf Bug #92 fix. 2015-10-26 21:55:04 +08:00
969c5e76ef Fix bytes not string. 2015-10-18 22:03:29 +08:00
7346326bc3 Fix video's thumb type forgot de_json. 2015-10-17 02:30:22 +08:00
f355796b01 Merge pull request #88 from mabitte/develop
Little code cleanup
2015-10-17 02:08:34 +08:00
5175803d0b * dict.get(key) returns None if key is not in the dictionary
* used the class method's class reference to instantiate type classes
2015-10-15 16:53:59 +02:00
bf9939d40e Fix queue default value. #86 2015-10-13 13:05:38 +08:00
07960fe348 Update new type chat document. 2015-10-12 23:00:25 +08:00
f4be18e082 Update Version.
Change log:
- New type Chat support.
- Bugs fix.
2015-10-12 22:45:01 +08:00
8e9837a587 Merge branch 'develop' 2015-10-12 22:37:17 +08:00
361c043872 Fix test data error. 2015-10-12 11:02:10 +08:00
50432dbef1 Fix test data error. 2015-10-12 10:54:33 +08:00
37277d74cc Fix testing error. 2015-10-12 10:51:29 +08:00
9ee341f5e6 Add Chat test for telebot. 2015-10-12 10:46:20 +08:00
0bd284afc7 Format. 2015-10-12 10:37:41 +08:00
4ffdada427 Add Chat json test. 2015-10-12 10:37:25 +08:00
d8effd3f9f New type Chat supported. 2015-10-12 10:36:58 +08:00
303e15b88d Merge pull request #84 from AndydeCleyre/patch-1
Fix AttributeError on Python 3
2015-10-09 21:57:05 +08:00
3273aa9afa Merge pull request #85 from AndydeCleyre/patch-2
Fix NameError in Python 3
2015-10-09 12:21:23 +08:00
9e8b11051c Fix NameError in Python 3 2015-10-08 16:46:15 -04:00
b9d458e643 Fix AttributeError on Python 3 2015-10-08 16:39:22 -04:00
1e6361dd57 Fix #83
Added a `encode('utf8')` statement to support non-ASCII symbols in error log messages.
2015-10-06 22:36:57 +02:00
ca2019b8f1 Merge pull request #82 from skgsergio/webhook-examples
Add Webhook examples (CPython, Flask, CherryPy)
2015-10-06 13:17:10 +02:00
a230665424 Update README.rst 2015-10-06 12:19:20 +02:00
8eb6e034fe Update README with webhooks information. 2015-10-06 12:11:42 +02:00
d6552eb4c6 Add webhook examples 2015-10-06 12:06:23 +02:00
117c5a1141 Minor CPU optimization 2015-10-03 12:48:56 +02:00
3f335c37ce Update version.
Change log:
- Add webhook support.
- Better error handling.
- Add threaded parameter for telebot constructor. Allowing users to decide telebot to use thread for polling method.
2015-10-03 16:33:17 +08:00
e7e681928d Fix polling stuch problem. 2015-10-02 23:24:54 +08:00
855ff40070 Fix python3 raise exception error. 2015-10-02 17:08:05 +08:00
29a42a398b Restored the non-threaded variant 2015-10-02 00:00:54 +02:00
b801728924 Fix unnecessary raise 2015-10-01 22:43:18 +02:00
d14e9051d4 Better error handling.
Errors now are re-raised in the Thread polling() was called from.
If none_stop is *not* set, ApiExceptions will cause the calling Thread to halt.
2015-10-01 22:03:54 +02:00
60ca1751ca WorkerThreads now log ApiExceptions 2015-10-01 11:33:23 +02:00
941b8ac5d0 Added webhooks support with set_webhook and remove_webhook in the TeleBot class 2015-09-30 18:19:31 +02:00
7a08102fad Merge branch 'master' of https://github.com/eternnoir/pyTelegramBotAPI into develop 2015-09-30 17:51:45 +02:00
325061ee96 Added "Bots using this API" section to docs
Added Channel link to docs
2015-09-30 17:50:40 +02:00
8839f36706 Update version.
Change log:
- Bug fix : fix race-condition for register_for_reply.
- Add timeout parameter for polling method.
2015-09-30 23:34:37 +08:00
1c53955d5a Add timeout para for polling method. 2015-09-30 23:18:26 +08:00
9cd28b88be Merge pull request #79 from huiyiqun/master
fix race-condition for register_for_reply
2015-09-30 22:50:08 +08:00
2fb2cd6f20 fix race-condition for message_subscribers 2015-09-30 20:46:37 +08:00
036d000b95 Remove 3.2/3.5 2015-09-22 20:34:06 +08:00
11d4bb02a8 Add Test python version 2015-09-22 20:25:35 +08:00
14 changed files with 758 additions and 197 deletions

View File

@ -2,6 +2,7 @@ language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "pypy"
- "pypy3"

View File

@ -2,8 +2,9 @@
<p align="center">A simple, but extensible Python implementation for the [Telegram Bot API](https://core.telegram.org/bots/api).
<p align="center">[![Build Status](https://travis-ci.org/eternnoir/pyTelegramBotAPI.svg?branch=master)](https://travis-ci.org/eternnoir/pyTelegramBotAPI)
[![Download Month](https://img.shields.io/pypi/v/pyTelegramBotAPI.svg)](https://pypi.python.org/pypi/pyTelegramBotAPI)
[![Build Status](https://travis-ci.org/eternnoir/pyTelegramBotAPI.svg?branch=master)](https://travis-ci.org/eternnoir/pyTelegramBotAPI)
[![Download Month](https://img.shields.io/pypi/dm/pyTelegramBotAPI.svg)](https://pypi.python.org/pypi/pyTelegramBotAPI)
* [Getting started.](#getting-started)
* [Writing your first bot](#writing-your-first-bot)
@ -27,6 +28,7 @@
* [How can I distinguish a User and a GroupChat in message.chat?](#how-can-i-distinguish-a-user-and-a-groupchat-in-messagechat)
* [The Telegram Chat Group](#the-telegram-chat-group)
* [More examples](#more-examples)
* [Bots using this API](#bots-using-this-api)
## Getting started.
@ -118,7 +120,7 @@ To start the bot, simply open up a terminal and enter `python echo_bot.py` to ru
All types are defined in types.py. They are all completely in line with the [Telegram API's definition of the types](https://core.telegram.org/bots/api#available-types), except for the Message's `from` field, which is renamed to `from_user` (because `from` is a Python reserved token). Thus, attributes such as `message_id` can be accessed directly with `message.message_id`. Note that `message.chat` can be either an instance of `User` or `GroupChat` (see [How can I distinguish a User and a GroupChat in message.chat?](#how-can-i-distinguish-a-user-and-a-groupchat-in-messagechat)).
The Message object also has a `content_type`attribute, which defines the type of the Message. `content_type` can be one of the following strings:
The Message object also has a `content_type`attribute, which defines the type of the Message. `content_type` can be one of the following strings:
'text', 'audio', 'document', 'photo', 'sticker', 'video', 'location', 'contact', 'new_chat_participant', 'left_chat_participant', 'new_chat_title', 'new_chat_photo', 'delete_chat_photo', 'group_chat_created'.
### Methods
@ -181,6 +183,13 @@ def test_message(message):
@bot.message_handler(func=test_message, content_types=['document'])
def handle_text_doc(message)
pass
# Handlers can be stacked to create a function which will be called if either message_handler is eligible
# This handler will be called if the message starts with '/hello' OR is some emoji
@bot.message_handler(commands=['hello'])
@bot.message_handler(func=lambda msg: msg.text.encode("utf-8") == SOME_FANCY_EMOJI)
def send_something(message):
pass
```
**Important: all handlers are tested in the order in which they were declared**
#### TeleBot
@ -200,6 +209,11 @@ tb.polling(none_stop=False, interval=0, block=True)
# getMe
user = tb.get_me()
# setWebhook
tb.set_webhook(url="http://example.com", certificate=open('mycert.pem'))
# unset webhook
tb.remove_webhook()
# getUpdates
updates = tb.get_updates()
updates = tb.get_updates(1234,100,20) #get_Updates(offset, limit, timeout):
@ -361,8 +375,10 @@ bot.set_update_listener(handle_messages)
bot.polling()
```
### Using web hooks
If you prefer using web hooks to the getUpdates method, you can use the `process_new_messages(messages)` function in TeleBot to make it process the messages that you supply. It takes a list of Message objects. This function is still incubating.
### Using webhooks
When using webhooks telegram sends one Update per call, for processing it you should call process_new_messages([update.message]) when you recieve it.
There are some examples using webhooks in the *examples/webhook_examples* directory.
### Logging
@ -379,21 +395,19 @@ telebot.logger.setLevel(logging.DEBUG) # Outputs debug messages to console.
## F.A.Q.
### How can I distinguish a User and a GroupChat in message.chat?
There are two ways to do this:
Telegram Bot API support new type Chat for message.chat.
- Checking the instance of message.chat with `isinstance`:
- Check the ```type``` attribute in ```Chat``` object:
```python
def is_user(chat):
return isinstance(chat, types.User)
if message.chat.type == private:
# private chat message
print is_user(message.chat) # True or False
```
- Checking whether the chat id is negative or positive. If the chat id is negative, the chat is a GroupChat, if it is positive, it is a User. Example:
```python
def is_user(chat):
return chat.id > 0
if message.chat.type == group:
# group chat message
if message.chat.type == channel:
# channel message
print is_user(message.chat) # True or False
```
## The Telegram Chat Group
@ -401,9 +415,14 @@ print is_user(message.chat) # True or False
Get help. Discuss. Chat.
Join the [pyTelegramBotAPI Telegram Chat Group](https://telegram.me/joinchat/067e22c60035523fda8f6025ee87e30b).
We now have a Telegram Channel as well! Keep yourself up to date with API changes, and [join it](https://telegram.me/pytelegrambotapi).
## More examples
* [Echo Bot](https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/echo_bot.py)
* [Deep Linking](https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/deep_linking.py)
* [next_step_handler Example](https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/step_example.py)
## Bots using this API
* [SiteAlert bot](https://telegram.me/SiteAlert_bot) ([source](https://github.com/ilteoood/SiteAlert-Python)) by *ilteoood* - Monitors websites and sends a notification on changes
Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh.

View File

@ -52,6 +52,7 @@ API <https://core.telegram.org/bots/api>`__.
- `The Telegram Chat Group <#the-telegram-chat-group>`__
- `More examples <#more-examples>`__
- `Bots using this API <#bots-using-this-api>`__
Getting started.
================
@ -213,9 +214,39 @@ Outlined below are some general use cases of the API.
Message handlers
~~~~~~~~~~~~~~~~
A message handler is a function which is decorated with the
``message_handler`` decorator of a TeleBot instance. The following
examples illustrate the possibilities of message handlers:
A message handler is a function that is decorated with the
``message_handler`` decorator of a TeleBot instance. Message handlers
consist of one or multiple filters. Each filter much return True for a
certain message in order for a message handler to become eligible to
handle that message. A message handler is declared in the following way
(provided ``bot`` is an instance of TeleBot):
.. code:: python
@bot.message_handler(filters)
def function_name(message):
bot.reply_to(message, "This is a message handler")
``function_name`` is not bound to any restrictions. Any function name is
permitted with message handlers. The function must accept at most one
argument, which will be the message that the function must handle.
``filters`` is a list of keyword arguments. A filter is declared in the
following manner: ``name=argument``. One handler may have multiple
filters. TeleBot supports the following filters:
+------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| name | argument(s) | Condition |
+==================+=============================================+=================================================================================================================================================================================+
| content\_types | list of strings (default ``['text']``) | ``True`` if message.content\_type is in the list of strings. |
+------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| regexp | a regular expression as a string | ``True`` if ``re.search(regexp_arg)`` returns ``True`` and ``message.content_type == 'text'`` (See `Python Regular Expressions <https://docs.python.org/2/library/re.html>`__ |
+------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| commands | list of strings | ``True`` if ``message.content_type == 'text'`` and ``message.text`` starts with a command that is in the list of strings. |
+------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| func | a function (lambda or function reference) | ``True`` if the lambda or function reference returns ``True`` |
+------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Here are some examples of using the filters and message handlers:
.. code:: python
@ -250,8 +281,15 @@ examples illustrate the possibilities of message handlers:
def handle_text_doc(message)
pass
*Note: all handlers are tested in the order in which they were declared*
#### TeleBot
# Handlers can be stacked to create a function which will be called if either message_handler is eligible
# This handler will be called if the message starts with '/hello' OR is some emoji
@bot.message_handler(commands=['hello'])
@bot.message_handler(func=lambda msg: msg.text.encode("utf-8") == SOME_FANCY_EMOJI)
def send_something(message):
pass
**Important: all handlers are tested in the order in which they were
declared** #### TeleBot
.. code:: python
@ -270,6 +308,11 @@ examples illustrate the possibilities of message handlers:
# getMe
user = tb.get_me()
# setWebhook
tb.set_webhook(url="http://example.com", certificate=open('mycert.pem'))
# unset webhook
tb.remove_webhook()
# getUpdates
updates = tb.get_updates()
updates = tb.get_updates(1234,100,20) #get_Updates(offset, limit, timeout):
@ -322,6 +365,14 @@ examples illustrate the possibilities of message handlers:
# 'record_audio', 'upload_audio', 'upload_document' or 'find_location'.
tb.send_chat_action(chat_id, action_string)
# getFile
# Downloading a file is straightforward
# Returns a File object
import requests
file_info = tb.get_file(file_id)
file = requests.get('https://api.telegram.org/file/bot{0}/{1}'.format(API_TOKEN, file_info.file_path))
Reply markup
~~~~~~~~~~~~
@ -465,29 +516,31 @@ function as a listener to TeleBot. Example:
bot.set_update_listener(handle_messages)
bot.polling()
Using web hooks
---------------
Using webhooks
--------------
If you prefer using web hooks to the getUpdates method, you can use the
``process_new_messages(messages)`` function in TeleBot to make it
process the messages that you supply. It takes a list of Message
objects. This function is still incubating.
When using webhooks telegram sends one Update per call, for processing
it you should call process\_new\_messages([update.message]) when you
recieve it.
There are some examples using webhooks in the
*examples/webhook\_examples* directory.
Logging
-------
You can use the Telebot module logger to log debug info about Telebot.
Use ``telebot.logger`` to get the logger of the TeleBot module.
Use ``telebot.logger`` to get the logger of the TeleBot module. It is
possible to add custom logging Handlers to the logger. Refer to the
`Python logging module
page <https://docs.python.org/2/library/logging.html>`__ for more info.
.. code:: python
import logging
logger = telebot.logger
formatter = logging.Formatter('[%(asctime)s] %(thread)d {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s',
'%m-%d %H:%M:%S')
ch = logging.StreamHandler(sys.stdout)
logger.addHandler(ch)
logger.setLevel(logging.DEBUG) # or use logging.INFO
ch.setFormatter(formatter)
telebot.logger.setLevel(logging.DEBUG) # Outputs debug messages to console.
F.A.Q.
======
@ -513,6 +566,8 @@ Get help. Discuss. Chat.
Join the `pyTelegramBotAPI Telegram Chat
Group <https://telegram.me/joinchat/067e22c60035523fda8f6025ee87e30b>`__.
We now have a Telegram Channel as well! Keep yourself up to date with
API changes, and `join it <https://telegram.me/pytelegrambotapi>`__.
More examples
=============
@ -524,5 +579,14 @@ More examples
- `next\_step\_handler
Example <https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/step_example.py>`__
Bots using this API
===================
- `SiteAlert bot <https://telegram.me/SiteAlert_bot>`__
(`source <https://github.com/ilteoood/SiteAlert-Python>`__) by
*ilteoood* - Monitors websites and sends a notification on changes
Want to have your bot listed here? Send a Telegram message to
@eternnoir or @pevdh.
.. |Build Status| image:: https://travis-ci.org/eternnoir/pyTelegramBotAPI.svg?branch=master
:target: https://travis-ci.org/eternnoir/pyTelegramBotAPI

View File

@ -0,0 +1,36 @@
# Webhook examples using pyTelegramBotAPI
There are 3 examples in this directory using different libraries:
* **Python (CPython):** *webhook_cpython_echo_bot.py*
* **Pros:**
* Official python libraries, it works out of the box (doesn't require to
install anything).
* Works with Python 2 and Python 3 (need to be converted with 2to3).
* **Cons:**
* Ugly code.
* Many things to handle yourself, this can lead to errors.
* Not powerful, do the trick but the performance is low.
* **CherryPy (3.8.0):** *webhook_cherrypy_echo_bot.py*
* **Pros:**
* It's a web application framework, cleaner code, uses objects for defining
the web application.
* Very good performance.
* The project seems to be active, latest version is recent.
* Works with Python 2 and Python 3.
* **Cons:**
* Some things are not very intuitive, reading the doc is a must.
* **Flask (0.10.1):** *webhook_flask_echo_bot.py*
* **Pros:**
* It's a web application framework, cleaner code, uses decorator which can
be nice.
* Good performance.
* It's intuitive if you know how web application works.
* **Cons:**
* The project seems not to be very active, latest version dates 2013.
* They don't recommend to use it with Python 3, but may work.
* May be a oversized for just handling webhook petitions.
*Latest update of this document: 2015-10-06*

View File

@ -0,0 +1,85 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This is a simple echo bot using decorators and webhook with CherryPy
# It echoes any incoming text messages and does not use the polling method.
import cherrypy
import telebot
import logging
API_TOKEN = '<api_token>'
WEBHOOK_HOST = '<ip/host where the bot is running>'
WEBHOOK_PORT = 8443 # 443, 80, 88 or 8443 (port need to be 'open')
WEBHOOK_LISTEN = '0.0.0.0' # In some VPS you may need to put here the IP addr
WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate
WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
# Quick'n'dirty SSL certificate generation:
#
# openssl genrsa -out webhook_pkey.pem 2048
# openssl req -new -x509 -days 3650 -key webhook_pkey.pem -out webhook_cert.pem
#
# When asked for "Common Name (e.g. server FQDN or YOUR name)" you should reply
# with the same value in you put in WEBHOOK_HOST
WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT)
WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN)
logger = telebot.logger
telebot.logger.setLevel(logging.INFO)
bot = telebot.TeleBot(API_TOKEN)
# WebhookServer, process webhook calls
class WebhookServer(object):
@cherrypy.expose
def index(self):
if 'content-length' in cherrypy.request.headers and \
'content-type' in cherrypy.request.headers and \
cherrypy.request.headers['content-type'] == 'application/json':
length = int(cherrypy.request.headers['content-length'])
json_string = cherrypy.request.body.read(length).decode("utf-8")
update = telebot.types.Update.de_json(json_string)
bot.process_new_messages([update.message])
return ''
else:
raise cherrypy.HTTPError(403)
# Handle '/start' and '/help'
@bot.message_handler(commands=['help', 'start'])
def send_welcome(message):
bot.reply_to(message,
("Hi there, I am EchoBot.\n"
"I am here to echo your kind words back to you."))
# Handle all other messages
@bot.message_handler(func=lambda message: True, content_types=['text'])
def echo_message(message):
bot.reply_to(message, message.text)
# Remove webhook, it fails sometimes the set if there is a previous webhook
bot.remove_webhook()
# Set webhook
bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH,
certificate=open(WEBHOOK_SSL_CERT, 'r'))
# Start cherrypy server
cherrypy.config.update({
'server.socket_host': WEBHOOK_LISTEN,
'server.socket_port': WEBHOOK_PORT,
'server.ssl_module': 'builtin',
'server.ssl_certificate': WEBHOOK_SSL_CERT,
'server.ssl_private_key': WEBHOOK_SSL_PRIV
})
cherrypy.quickstart(WebhookServer(), WEBHOOK_URL_PATH, {'/': {}})

View File

@ -0,0 +1,99 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This is a simple echo bot using decorators and webhook with BaseHTTPServer
# It echoes any incoming text messages and does not use the polling method.
import BaseHTTPServer
import ssl
import telebot
import logging
API_TOKEN = '<api_token>'
WEBHOOK_HOST = '<ip/host where the bot is running>'
WEBHOOK_PORT = 8443 # 443, 80, 88 or 8443 (port need to be 'open')
WEBHOOK_LISTEN = '0.0.0.0' # In some VPS you may need to put here the IP addr
WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate
WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
# Quick'n'dirty SSL certificate generation:
#
# openssl genrsa -out webhook_pkey.pem 2048
# openssl req -new -x509 -days 3650 -key webhook_pkey.pem -out webhook_cert.pem
#
# When asked for "Common Name (e.g. server FQDN or YOUR name)" you should reply
# with the same value in you put in WEBHOOK_HOST
WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT)
WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN)
logger = telebot.logger
telebot.logger.setLevel(logging.INFO)
bot = telebot.TeleBot(API_TOKEN)
# WebhookHandler, process webhook calls
class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler):
server_version = "WebhookHandler/1.0"
def do_HEAD(self):
self.send_response(200)
self.end_headers()
def do_GET(self):
self.send_response(200)
self.end_headers()
def do_POST(self):
if self.path == WEBHOOK_URL_PATH and \
'content-type' in self.headers and \
'content-length' in self.headers and \
self.headers['content-type'] == 'application/json':
json_string = self.rfile.read(int(self.headers['content-length']))
self.send_response(200)
self.end_headers()
update = telebot.types.Update.de_json(json_string)
bot.process_new_messages([update.message])
else:
self.send_error(403)
self.end_headers()
# Handle '/start' and '/help'
@bot.message_handler(commands=['help', 'start'])
def send_welcome(message):
bot.reply_to(message,
("Hi there, I am EchoBot.\n"
"I am here to echo your kind words back to you."))
# Handle all other messages
@bot.message_handler(func=lambda message: True, content_types=['text'])
def echo_message(message):
bot.reply_to(message, message.text)
# Remove webhook, it fails sometimes the set if there is a previous webhook
bot.remove_webhook()
# Set webhook
bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH,
certificate=open(WEBHOOK_SSL_CERT, 'r'))
# Start server
httpd = BaseHTTPServer.HTTPServer((WEBHOOK_LISTEN, WEBHOOK_PORT),
WebhookHandler)
httpd.socket = ssl.wrap_socket(httpd.socket,
certfile=WEBHOOK_SSL_CERT,
keyfile=WEBHOOK_SSL_PRIV,
server_side=True)
httpd.serve_forever()

View File

@ -0,0 +1,84 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This is a simple echo bot using decorators and webhook with flask
# It echoes any incoming text messages and does not use the polling method.
import flask
import telebot
import logging
API_TOKEN = '<api_token>'
WEBHOOK_HOST = '<ip/host where the bot is running>'
WEBHOOK_PORT = 8443 # 443, 80, 88 or 8443 (port need to be 'open')
WEBHOOK_LISTEN = '0.0.0.0' # In some VPS you may need to put here the IP addr
WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate
WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
# Quick'n'dirty SSL certificate generation:
#
# openssl genrsa -out webhook_pkey.pem 2048
# openssl req -new -x509 -days 3650 -key webhook_pkey.pem -out webhook_cert.pem
#
# When asked for "Common Name (e.g. server FQDN or YOUR name)" you should reply
# with the same value in you put in WEBHOOK_HOST
WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT)
WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN)
logger = telebot.logger
telebot.logger.setLevel(logging.INFO)
bot = telebot.TeleBot(API_TOKEN)
app = flask.Flask(__name__)
# Empty webserver index, return nothing, just http 200
@app.route('/', methods=['GET', 'HEAD'])
def index():
return ''
# Process webhook calls
@app.route(WEBHOOK_URL_PATH, methods=['POST'])
def webhook():
if flask.request.headers.get('content-type') == 'application/json':
json_string = flask.request.get_data()
update = telebot.types.Update.de_json(json_string)
bot.process_new_messages([update.message])
return ''
else:
flask.abort(403)
# Handle '/start' and '/help'
@bot.message_handler(commands=['help', 'start'])
def send_welcome(message):
bot.reply_to(message,
("Hi there, I am EchoBot.\n"
"I am here to echo your kind words back to you."))
# Handle all other messages
@bot.message_handler(func=lambda message: True, content_types=['text'])
def echo_message(message):
bot.reply_to(message, message.text)
# Remove webhook, it fails sometimes the set if there is a previous webhook
bot.remove_webhook()
# Set webhook
bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH,
certificate=open(WEBHOOK_SSL_CERT, 'r'))
# Start flask server
app.run(host=WEBHOOK_LISTEN,
port=WEBHOOK_PORT,
ssl_context=(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV),
debug=True)

View File

@ -6,7 +6,7 @@ def readme():
return f.read()
setup(name='pyTelegramBotAPI',
version='0.3.6',
version='1.4.0',
description='Python Telegram bot api. ',
long_description=readme(),
author='eternnoir',

View File

@ -8,8 +8,11 @@ import sys
import six
import logging
logger = logging.getLogger('TeleBot')
formatter = logging.Formatter('%(asctime)s (%(filename)s:%(lineno)d) %(levelname)s - %(name)s: "%(message)s"')
formatter = logging.Formatter(
'%(asctime)s (%(filename)s:%(lineno)d %(threadName)s) %(levelname)s - %(name)s: "%(message)s"'
)
console_output_handler = logging.StreamHandler(sys.stderr)
console_output_handler.setFormatter(formatter)
@ -22,6 +25,8 @@ from telebot import apihelper, types, util
"""
Module : telebot
"""
class TeleBot:
""" This is TeleBot Class
Methods:
@ -39,30 +44,37 @@ class TeleBot:
getUpdates
"""
def __init__(self, token, create_threads=True, num_threads=4):
def __init__(self, token, threaded=True):
"""
:param token: bot API token
:param create_threads: Create thread for message handler
:param num_threads: Number of worker in thread pool.
:return: Telebot object.
"""
self.token = token
self.update_listener = []
self.polling_thread = None
self.__stop_polling = threading.Event()
self.last_update_id = 0
self.num_threads = num_threads
self.__create_threads = create_threads
self.exc_info = None
self.message_subscribers_messages = []
self.message_subscribers_callbacks = []
self.message_subscribers_lock = threading.Lock()
# key: chat_id, value: handler list
self.message_subscribers_next_step = {}
self.pre_message_subscribers_next_step = {}
self.message_handlers = []
if self.__create_threads:
self.worker_pool = util.ThreadPool(num_threads)
self.threaded = threaded
if self.threaded:
self.worker_pool = util.ThreadPool()
def set_webhook(self, url=None, certificate=None):
return apihelper.set_webhook(self.token, url, certificate)
def remove_webhook(self):
return self.set_webhook() # No params resets webhook
def get_updates(self, offset=None, limit=None, timeout=20):
"""
@ -78,13 +90,13 @@ class TeleBot:
ret.append(types.Update.de_json(ju))
return ret
def get_update(self):
def __retrieve_updates(self, timeout=20):
"""
Retrieves any updates from the Telegram API.
Registered listeners and applicable message handlers will be notified when a new message arrives.
:raises ApiException when a call has failed.
"""
updates = self.get_updates(offset=(self.last_update_id + 1), timeout=3)
updates = self.get_updates(offset=(self.last_update_id + 1), timeout=timeout)
new_messages = []
for update in updates:
if update.update_id > self.last_update_id:
@ -95,6 +107,7 @@ class TeleBot:
self.process_new_messages(new_messages)
def process_new_messages(self, new_messages):
self._append_pre_next_step_handler()
self.__notify_update(new_messages)
self._notify_command_handlers(new_messages)
self._notify_message_subscribers(new_messages)
@ -102,59 +115,98 @@ class TeleBot:
def __notify_update(self, new_messages):
for listener in self.update_listener:
if self.__create_threads:
self.worker_pool.put(listener, new_messages)
else:
listener(new_messages)
self.__exec_task(listener, new_messages)
def polling(self, none_stop=False, interval=0, block=True):
def polling(self, none_stop=False, interval=0, timeout=20):
"""
This function creates a new Thread that calls an internal __polling function.
This function creates a new Thread that calls an internal __retrieve_updates function.
This allows the bot to retrieve Updates automagically and notify listeners and message handlers accordingly.
Do not call this function more than once!
Warning: Do not call this function more than once!
Always get updates.
:param none_stop: Do not stop polling when Exception occur.
:param none_stop: Do not stop polling when an ApiException occurs.
:param timeout: Timeout in seconds for long polling.
:return:
"""
self.__stop_polling.set()
if self.polling_thread:
self.polling_thread.join() # wait thread stop.
self.__stop_polling.clear()
self.polling_thread = threading.Thread(target=self.__polling, args=([none_stop, interval]))
self.polling_thread.daemon = True
self.polling_thread.start()
if self.threaded:
self.__threaded_polling(none_stop, interval, timeout)
else:
self.__non_threaded_polling(none_stop, interval, timeout)
if block:
while self.polling_thread.is_alive:
try:
time.sleep(.1)
except KeyboardInterrupt:
logger.info("Received KeyboardInterrupt. Stopping.")
self.stop_polling()
self.polling_thread.join()
break
def __polling(self, none_stop, interval):
def __threaded_polling(self, none_stop=False, interval=0, timeout=3):
logger.info('Started polling.')
self.__stop_polling.clear()
error_interval = .25
polling_thread = util.WorkerThread(name="PollingThread")
or_event = util.OrEvent(
polling_thread.done_event,
polling_thread.exception_event,
self.worker_pool.exception_event
)
while not self.__stop_polling.wait(interval):
or_event.clear()
try:
self.get_update()
polling_thread.put(self.__retrieve_updates, timeout)
or_event.wait() # wait for polling thread finish, polling thread error or thread pool error
polling_thread.raise_exceptions()
self.worker_pool.raise_exceptions()
error_interval = .25
except apihelper.ApiException as e:
logger.error(e)
if not none_stop:
self.__stop_polling.set()
logger.info("Exception occurred. Stopping.")
else:
polling_thread.clear_exceptions()
self.worker_pool.clear_exceptions()
logger.info("Waiting for {0} seconds until retry".format(error_interval))
time.sleep(error_interval)
error_interval *= 2
logger.error(e)
except KeyboardInterrupt:
logger.info("KeyboardInterrupt received.")
self.__stop_polling.set()
polling_thread.stop()
break
logger.info('Stopped polling.')
def __non_threaded_polling(self, none_stop=False, interval=0, timeout=3):
logger.info('Started polling.')
self.__stop_polling.clear()
error_interval = .25
while not self.__stop_polling.wait(interval):
try:
self.__retrieve_updates(timeout)
error_interval = .25
except apihelper.ApiException as e:
logger.error(e)
if not none_stop:
self.__stop_polling.set()
logger.info("Exception occurred. Stopping.")
else:
logger.info("Waiting for {0} seconds until retry".format(error_interval))
time.sleep(error_interval)
error_interval *= 2
except KeyboardInterrupt:
logger.info("KeyboardInterrupt received.")
self.__stop_polling.set()
break
logger.info('Stopped polling.')
def __exec_task(self, task, *args, **kwargs):
if self.threaded:
self.worker_pool.put(task, *args, **kwargs)
else:
task(*args, **kwargs)
def stop_polling(self):
self.__stop_polling.set()
@ -336,11 +388,12 @@ class TeleBot:
:param callback: The callback function to be called when a reply arrives. Must accept one `message`
parameter, which will contain the replied message.
"""
self.message_subscribers_messages.insert(0, message.message_id)
self.message_subscribers_callbacks.insert(0, callback)
if len(self.message_subscribers_messages) > 10000:
self.message_subscribers_messages.pop()
self.message_subscribers_callbacks.pop()
with self.message_subscribers_lock:
self.message_subscribers_messages.insert(0, message.message_id)
self.message_subscribers_callbacks.insert(0, callback)
if len(self.message_subscribers_messages) > 10000:
self.message_subscribers_messages.pop()
self.message_subscribers_callbacks.pop()
def _notify_message_subscribers(self, new_messages):
for message in new_messages:
@ -352,8 +405,10 @@ class TeleBot:
index = self.message_subscribers_messages.index(reply_msg_id)
self.message_subscribers_callbacks[index](message)
del self.message_subscribers_messages[index]
del self.message_subscribers_callbacks[index]
with self.message_subscribers_lock:
index = self.message_subscribers_messages.index(reply_msg_id)
del self.message_subscribers_messages[index]
del self.message_subscribers_callbacks[index]
def register_next_step_handler(self, message, callback):
"""
@ -363,10 +418,10 @@ class TeleBot:
:param callback: The callback function which next new message arrives.
"""
chat_id = message.chat.id
if chat_id in self.message_subscribers_next_step:
self.message_subscribers_next_step[chat_id].append(callback)
if chat_id in self.pre_message_subscribers_next_step:
self.pre_message_subscribers_next_step[chat_id].append(callback)
else:
self.message_subscribers_next_step[chat_id] = [callback]
self.pre_message_subscribers_next_step[chat_id] = [callback]
def _notify_message_next_handler(self, new_messages):
for message in new_messages:
@ -374,9 +429,17 @@ class TeleBot:
if chat_id in self.message_subscribers_next_step:
handlers = self.message_subscribers_next_step[chat_id]
for handler in handlers:
self.worker_pool.put(handler, message)
self.__exec_task(handler, message)
self.message_subscribers_next_step.pop(chat_id, None)
def _append_pre_next_step_handler(self):
for k in self.pre_message_subscribers_next_step.keys():
if k in self.message_subscribers_next_step:
self.message_subscribers_next_step[k].extend(self.pre_message_subscribers_next_step[k])
else:
self.message_subscribers_next_step[k] = self.pre_message_subscribers_next_step[k]
self.pre_message_subscribers_next_step = {}
def message_handler(self, commands=None, regexp=None, func=None, content_types=['text']):
"""
Message handler decorator.
@ -406,6 +469,7 @@ class TeleBot:
: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'].
"""
def decorator(fn):
handler_dict = {'function': fn}
filters = {'content_types': content_types}
@ -444,10 +508,7 @@ class TeleBot:
for message in new_messages:
for message_handler in self.message_handlers:
if self._test_message_handler(message_handler, message):
if self.__create_threads:
self.worker_pool.put(message_handler['function'], message)
else:
message_handler['function'](message)
self.__exec_task(message_handler['function'], message)
break

View File

@ -43,14 +43,14 @@ def _check_result(method_name, result):
"""
if result.status_code != 200:
msg = 'The server returned HTTP {0} {1}. Response body:\n[{2}]'\
.format(result.status_code, result.reason, result.text)
.format(result.status_code, result.reason, result.text.encode('utf8'))
raise ApiException(msg, method_name, result)
try:
result_json = result.json()
except:
msg = 'The server returned an invalid JSON response. Response body:\n[{0}]'\
.format(result.text)
.format(result.text.encode('utf8'))
raise ApiException(msg, method_name, result)
if not result_json['ok']:
@ -105,6 +105,18 @@ def send_message(token, chat_id, text, disable_web_page_preview=None, reply_to_m
return _make_request(token, method_url, params=payload, method='post')
def set_webhook(token, url=None, certificate=None):
method_url = 'setWebhook'
payload = {
'url': url if url else "",
}
files = None
if certificate:
files = {'certificate': certificate}
return _make_request(token, method_url, params=payload, files=files)
def get_updates(token, offset=None, limit=None, timeout=None):
method_url = r'getUpdates'
payload = {}

View File

@ -21,6 +21,7 @@ ForceReply
"""
import json
import six
class JsonSerializable:
@ -28,6 +29,7 @@ class JsonSerializable:
Subclasses of this class are guaranteed to be able to be converted to JSON format.
All subclasses of this class must override to_json.
"""
def to_json(self):
"""
Returns a JSON string representation of this class.
@ -43,6 +45,7 @@ class JsonDeserializable:
Subclasses of this class are guaranteed to be able to be created from a json-style dict or json formatted string.
All subclasses of this class must override de_json.
"""
@classmethod
def de_json(cls, json_type):
"""
@ -70,13 +73,14 @@ class JsonDeserializable:
def __str__(self):
d = {}
for x, y in self.__dict__.iteritems():
for x, y in six.iteritems(self.__dict__):
if hasattr(y, '__dict__'):
d[x] = y.__dict__
else:
d[x] = y
return unicode(d)
return six.text_type(d)
class Update(JsonDeserializable):
@classmethod
@ -84,25 +88,22 @@ class Update(JsonDeserializable):
obj = cls.check_json(json_type)
update_id = obj['update_id']
message = Message.de_json(obj['message'])
return Update(update_id, message)
return cls(update_id, message)
def __init__(self, update_id, message):
self.update_id = update_id
self.message = message
class User(JsonDeserializable):
@classmethod
def de_json(cls, json_string):
obj = cls.check_json(json_string)
id = obj['id']
first_name = obj['first_name']
last_name = None
username = None
if 'last_name' in obj:
last_name = obj['last_name']
if 'username' in obj:
username = obj['username']
return User(id, first_name, last_name, username)
last_name = obj.get('last_name')
username = obj.get('username')
return cls(id, first_name, last_name, username)
def __init__(self, id, first_name, last_name=None, username=None):
self.id = id
@ -117,20 +118,43 @@ class GroupChat(JsonDeserializable):
obj = cls.check_json(json_string)
id = obj['id']
title = obj['title']
return GroupChat(id, title)
return cls(id, title)
def __init__(self, id, title):
self.id = id
self.title = title
class Chat(JsonDeserializable):
@classmethod
def de_json(cls, json_string):
obj = cls.check_json(json_string)
id = obj['id']
type = obj['type']
title = obj.get('title')
username = obj.get('username')
first_name = obj.get('first_name')
last_name = obj.get('last_name')
return cls(id, type, title, username, first_name, last_name)
def __init__(self, id, type, title=None, username=None, first_name=None, last_name=None):
self.type = type
self.last_name = last_name
self.first_name = first_name
self.username = username
self.id = id
self.title = title
class Message(JsonDeserializable):
@classmethod
def de_json(cls, json_string):
obj = cls.check_json(json_string)
message_id = obj['message_id']
from_user = User.de_json(obj['from'])
chat = Message.parse_chat(obj['chat'])
from_user = None
if 'from' in obj:
from_user = User.de_json(obj['from'])
chat = Chat.de_json(obj['chat'])
date = obj['date']
content_type = None
opts = {}
@ -187,7 +211,7 @@ class Message(JsonDeserializable):
content_type = 'group_chat_created'
if 'caption' in obj:
opts['caption'] = obj['caption']
return Message(message_id, from_user, date, chat, content_type, opts)
return cls(message_id, from_user, date, chat, content_type, opts)
@classmethod
def parse_chat(cls, chat):
@ -220,10 +244,8 @@ class PhotoSize(JsonDeserializable):
file_id = obj['file_id']
width = obj['width']
height = obj['height']
file_size = None
if 'file_size' in obj:
file_size = obj['file_size']
return PhotoSize(file_id, width, height, file_size)
file_size = obj.get('file_size')
return cls(file_id, width, height, file_size)
def __init__(self, file_id, width, height, file_size=None):
self.file_size = file_size
@ -238,19 +260,11 @@ class Audio(JsonDeserializable):
obj = cls.check_json(json_string)
file_id = obj['file_id']
duration = obj['duration']
performer = None
title = None
mime_type = None
file_size = None
if 'mime_type' in obj:
mime_type = obj['mime_type']
if 'file_size' in obj:
file_size = obj['file_size']
if 'performer' in obj:
performer = obj['performer']
if 'title' in obj:
title = obj['title']
return Audio(file_id, duration, performer, title, mime_type, file_size)
performer = obj.get('performer')
title = obj.get('title')
mime_type = obj.get('mime_type')
file_size = obj.get('file_size')
return cls(file_id, duration, performer, title, mime_type, file_size)
def __init__(self, file_id, duration, performer=None, title=None, mime_type=None, file_size=None):
self.file_id = file_id
@ -267,13 +281,9 @@ class Voice(JsonDeserializable):
obj = cls.check_json(json_string)
file_id = obj['file_id']
duration = obj['duration']
mime_type = None
file_size = None
if 'mime_type' in obj:
mime_type = obj['mime_type']
if 'file_size' in obj:
file_size = obj['file_size']
return Voice(file_id, duration, mime_type, file_size)
mime_type = obj.get('mime_type')
file_size = obj.get('file_size')
return cls(file_id, duration, mime_type, file_size)
def __init__(self, file_id, duration, mime_type=None, file_size=None):
self.file_id = file_id
@ -288,19 +298,12 @@ class Document(JsonDeserializable):
obj = cls.check_json(json_string)
file_id = obj['file_id']
thumb = None
if 'thumb' in obj:
if 'file_id' in obj['thumb']:
thumb = PhotoSize.de_json(obj['thumb'])
file_name = None
mime_type = None
file_size = None
if 'file_name' in obj:
file_name = obj['file_name']
if 'mime_type' in obj:
mime_type = obj['mime_type']
if 'file_size' in obj:
file_size = obj['file_size']
return Document(file_id, thumb, file_name, mime_type, file_size)
if 'thumb' in obj and 'file_id' in obj['thumb']:
thumb = PhotoSize.de_json(obj['thumb'])
file_name = obj.get('file_name')
mime_type = obj.get('mime_type')
file_size = obj.get('file_size')
return cls(file_id, thumb, file_name, mime_type, file_size)
def __init__(self, file_id, thumb, file_name=None, mime_type=None, file_size=None):
self.file_id = file_id
@ -320,10 +323,8 @@ class Sticker(JsonDeserializable):
thumb = None
if 'thumb' in obj:
thumb = PhotoSize.de_json(obj['thumb'])
file_size = None
if 'file_size' in obj:
file_size = obj['file_size']
return Sticker(file_id, width, height, thumb, file_size)
file_size = obj.get('file_size')
return cls(file_id, width, height, thumb, file_size)
def __init__(self, file_id, width, height, thumb, file_size=None):
self.file_id = file_id
@ -342,15 +343,11 @@ class Video(JsonDeserializable):
height = obj['height']
duration = obj['duration']
thumb = None
mime_type = None
file_size = None
if 'thumb' in obj:
thumb = PhotoSize.de_json(obj['thumb'])
if 'mime_type' in obj:
mime_type = obj['mime_type']
if 'file_size' in obj:
file_size = obj['file_size']
return Video(file_id, width, height, duration, thumb, mime_type, file_size)
mime_type = obj.get('mime_type')
file_size = obj.get('file_size')
return cls(file_id, width, height, duration, thumb, mime_type, file_size)
def __init__(self, file_id, width, height, duration, thumb=None, mime_type=None, file_size=None):
self.file_id = file_id
@ -368,13 +365,9 @@ class Contact(JsonDeserializable):
obj = cls.check_json(json_string)
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:
user_id = obj['user_id']
return Contact(phone_number, first_name, last_name, user_id)
last_name = obj.get('last_name')
user_id = obj.get('user_id')
return cls(phone_number, first_name, last_name, user_id)
def __init__(self, phone_number, first_name, last_name=None, user_id=None):
self.phone_number = phone_number
@ -389,7 +382,7 @@ class Location(JsonDeserializable):
obj = cls.check_json(json_string)
longitude = obj['longitude']
latitude = obj['latitude']
return Location(longitude, latitude)
return cls(longitude, latitude)
def __init__(self, longitude, latitude):
self.longitude = longitude
@ -402,26 +395,21 @@ class UserProfilePhotos(JsonDeserializable):
obj = cls.check_json(json_string)
total_count = obj['total_count']
photos = [[PhotoSize.de_json(y) for y in x] for x in obj['photos']]
return UserProfilePhotos(total_count, photos)
return cls(total_count, photos)
def __init__(self, total_count, photos):
self.total_count = total_count
self.photos = photos
class File(JsonDeserializable):
class File(JsonDeserializable):
@classmethod
def de_json(cls, json_type):
obj = cls.check_json(json_type)
file_id = obj['file_id']
file_size = None
file_path = None
if 'file_size' in obj:
file_size = obj['file_size']
if 'file_path' in obj:
file_path = obj['file_path']
return File(file_id, file_size, file_path)
file_size = obj.get('file_size')
file_path = obj.get('file_path')
return cls(file_id, file_size, file_path)
def __init__(self, file_id, file_size, file_path):
self.file_id = file_id

View File

@ -1,54 +1,116 @@
# -*- coding: utf-8 -*-
import threading
import sys
import six
from six import string_types
# Python3 queue support.
try:
import Queue
except ImportError:
import queue as Queue
from telebot import logger
class ThreadPool:
class WorkerThread(threading.Thread):
class WorkerThread(threading.Thread):
count = 0
def __init__(self, queue):
threading.Thread.__init__(self, name="WorkerThread{0}".format(self.__class__.count + 1))
self.__class__.count += 1
def __init__(self, exception_callback=None, queue=None, name=None):
if not name:
name = "WorkerThread{0}".format(self.__class__.count + 1)
self.__class__.count += 1
if not queue:
queue = Queue.Queue()
threading.Thread.__init__(self, name=name)
self.queue = queue
self.daemon = True
self.received_task_event = threading.Event()
self.done_event = threading.Event()
self.exception_event = threading.Event()
self.continue_event = threading.Event()
self.exception_callback = exception_callback
self.exc_info = None
self._running = True
self.start()
def run(self):
while self._running:
try:
task, args, kwargs = self.queue.get(block=True, timeout=.01)
task, args, kwargs = self.queue.get(block=True, timeout=.5)
self.continue_event.clear()
self.received_task_event.clear()
self.done_event.clear()
self.exception_event.clear()
logger.debug("Received task")
self.received_task_event.set()
task(*args, **kwargs)
logger.debug("Task complete")
self.done_event.set()
except Queue.Empty:
pass
except:
logger.debug("Exception occurred")
self.exc_info = sys.exc_info()
self.exception_event.set()
if self.exception_callback:
self.exception_callback(self, self.exc_info)
self.continue_event.wait()
def put(self, task, *args, **kwargs):
self.queue.put((task, args, kwargs))
def raise_exceptions(self):
if self.exception_event.is_set():
six.reraise(self.exc_info[0], self.exc_info[1], self.exc_info[2])
def clear_exceptions(self):
self.exception_event.clear()
self.continue_event.set()
def stop(self):
self._running = False
def __init__(self, num_threads=4):
self.tasks = Queue.Queue()
self.workers = [self.WorkerThread(self.tasks) for _ in range(num_threads)]
class ThreadPool:
def __init__(self, num_threads=2):
self.tasks = Queue.Queue()
self.workers = [WorkerThread(self.on_exception, self.tasks) for _ in range(num_threads)]
self.num_threads = num_threads
self.exception_event = threading.Event()
self.exc_info = None
def put(self, func, *args, **kwargs):
self.tasks.put((func, args, kwargs))
def on_exception(self, worker_thread, exc_info):
self.exc_info = exc_info
self.exception_event.set()
worker_thread.continue_event.set()
def raise_exceptions(self):
if self.exception_event.is_set():
six.reraise(self.exc_info[0], self.exc_info[1], self.exc_info[2])
def clear_exceptions(self):
self.exception_event.clear()
def close(self):
for worker in self.workers:
worker.stop()
for worker in self.workers:
worker.join()
class AsyncTask:
def __init__(self, target, *args, **kwargs):
self.target = target
@ -62,15 +124,15 @@ class AsyncTask:
def _run(self):
try:
self.result = self.target(*self.args, **self.kwargs)
except Exception as e:
self.result = e
except:
self.result = sys.exc_info()
self.done = True
def wait(self):
if not self.done:
self.thread.join()
if isinstance(self.result, Exception):
raise self.result
if isinstance(self.result, BaseException):
six.reraise(self.result[0], self.result[1], self.result[2])
else:
return self.result
@ -124,3 +186,41 @@ def split_string(text, chars_per_string):
:return: The splitted text as a list of strings.
"""
return [text[i:i + chars_per_string] for i in range(0, len(text), chars_per_string)]
# CREDITS TO http://stackoverflow.com/questions/12317940#answer-12320352
def or_set(self):
self._set()
self.changed()
def or_clear(self):
self._clear()
self.changed()
def orify(e, changed_callback):
e._set = e.set
e._clear = e.clear
e.changed = changed_callback
e.set = lambda: or_set(e)
e.clear = lambda: or_clear(e)
def OrEvent(*events):
or_event = threading.Event()
def changed():
bools = [e.is_set() for e in events]
if any(bools):
or_event.set()
else:
or_event.clear()
def busy_wait():
while not or_event.is_set():
or_event._wait(3)
for e in events:
orify(e, changed)
or_event._wait = or_event.wait
or_event.wait = busy_wait
changed()
return or_event

View File

@ -101,7 +101,6 @@ class TestTeleBot:
ret_msg = tb.send_message(CHAT_ID, markdown, parse_mode="Markdown")
assert ret_msg.message_id
def test_send_file(self):
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
tb = telebot.TeleBot(TOKEN)
@ -204,6 +203,13 @@ class TestTeleBot:
assert int(ret_msg.location.longitude) == int(lon)
assert int(ret_msg.location.latitude) == int(lat)
def test_Chat(self):
tb = telebot.TeleBot(TOKEN)
me = tb.get_me()
msg = tb.send_message(CHAT_ID, 'Test')
assert me.id == msg.from_user.id
assert msg.chat.id == int(CHAT_ID)
def create_text_message(self, text):
params = {'text': text}
chat = types.User(11, 'test')
@ -220,4 +226,3 @@ class TestTeleBot:
def test_not_string(self):
i1 = 10
assert not util.is_string(i1)

View File

@ -12,13 +12,13 @@ def test_json_user():
def test_json_message():
jsonstring = r'{"message_id":1,"from":{"id":108929734,"first_name":"Frank","last_name":"Wang","username":"eternnoir"},"chat":{"id":108929734,"first_name":"Frank","last_name":"Wang","username":"eternnoir"},"date":1435296025,"text":"HIHI"}'
jsonstring = r'{"message_id":1,"from":{"id":108929734,"first_name":"Frank","last_name":"Wang","username":"eternnoir"},"chat":{"id":1734,"first_name":"F","type":"private","last_name":"Wa","username":"oir"},"date":1435296025,"text":"HIHI"}'
msg = types.Message.de_json(jsonstring)
assert msg.text == 'HIHI'
def test_json_message_group():
json_string = r'{"message_id":10,"from":{"id":12345,"first_name":"g","last_name":"G","username":"GG"},"chat":{"id":-866,"title":"\u4ea4"},"date":1435303157,"text":"HIHI"}'
json_string = r'{"message_id":10,"from":{"id":12345,"first_name":"g","last_name":"G","username":"GG"},"chat":{"id":-866,"type":"private","title":"\u4ea4"},"date":1435303157,"text":"HIHI"}'
msg = types.Message.de_json(json_string)
assert msg.text == 'HIHI'
assert len(msg.chat.title) != 0
@ -39,7 +39,7 @@ def test_json_Document():
def test_json_Message_Audio():
json_string = r'{"message_id":131,"from":{"id":12775,"first_name":"dd","username":"dd"},"chat":{"id":10834,"first_name":"dd","last_name":"dd","username":"dd"},"date":1439978364,"audio":{"duration":1,"mime_type":"audio\/mpeg","title":"pyTelegram","performer":"eternnoir","file_id":"BQADBQADDH1JaB8-1KyWUss2-Ag","file_size":20096}}'
json_string = r'{"message_id":131,"from":{"id":12775,"first_name":"dd","username":"dd"},"chat":{"id":10834,"first_name":"dd","type":"private","type":"private","last_name":"dd","username":"dd"},"date":1439978364,"audio":{"duration":1,"mime_type":"audio\/mpeg","title":"pyTelegram","performer":"eternnoir","file_id":"BQADBQADDH1JaB8-1KyWUss2-Ag","file_size":20096}}'
msg = types.Message.de_json(json_string)
assert msg.audio.duration == 1
assert msg.content_type == 'audio'
@ -48,7 +48,7 @@ def test_json_Message_Audio():
def test_json_Message_Sticker():
json_string = r'{"message_id":98,"from":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd"},"chat":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd"},"date":1435479551,"sticker":{"width":550,"height":368,"thumb":{"file_id":"AAQFABPJLB0sAAQq17w-li3bzoIfAAIC","file_size":1822,"width":90,"height":60},"file_id":"BQADBQADNAIAAsYifgYdGJOa6bGAsQI","file_size":30320}}'
json_string = r'{"message_id":98,"from":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd"},"chat":{"id":10734,"first_name":"Fd","type":"private","last_name":"Wd","username":"dd"},"date":1435479551,"sticker":{"width":550,"height":368,"thumb":{"file_id":"AAQFABPJLB0sAAQq17w-li3bzoIfAAIC","file_size":1822,"width":90,"height":60},"file_id":"BQADBQADNAIAAsYifgYdGJOa6bGAsQI","file_size":30320}}'
msg = types.Message.de_json(json_string)
assert msg.sticker.height == 368
assert msg.sticker.thumb.height == 60
@ -56,7 +56,7 @@ def test_json_Message_Sticker():
def test_json_Message_Sticker_without_thumb():
json_string = r'{"message_id":98,"from":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd"},"chat":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd"},"date":1435479551,"sticker":{"width":550,"height":368,"file_id":"BQADBQADNAIAAsYifgYdGJOa6bGAsQI","file_size":30320}}'
json_string = r'{"message_id":98,"from":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd"},"chat":{"id":10734,"first_name":"Fd","type":"private","last_name":"Wd","username":"dd"},"date":1435479551,"sticker":{"width":550,"height":368,"file_id":"BQADBQADNAIAAsYifgYdGJOa6bGAsQI","file_size":30320}}'
msg = types.Message.de_json(json_string)
assert msg.sticker.height == 368
assert msg.sticker.thumb == None
@ -64,21 +64,21 @@ def test_json_Message_Sticker_without_thumb():
def test_json_Message_Document():
json_string = r'{"message_id":97,"from":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd"},"chat":{"id":10,"first_name":"Fd","last_name":"Wd","username":"dd"},"date":1435478744,"document":{"file_name":"Text File","thumb":{},"file_id":"BQADBQADMwIAAsYifgZ_CEh0u682xwI","file_size":446}}'
json_string = r'{"message_id":97,"from":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd"},"chat":{"id":10,"first_name":"Fd","type":"private","last_name":"Wd","username":"dd"},"date":1435478744,"document":{"file_name":"Text File","thumb":{},"file_id":"BQADBQADMwIAAsYifgZ_CEh0u682xwI","file_size":446}}'
msg = types.Message.de_json(json_string)
assert msg.document.file_name == 'Text File'
assert msg.content_type == 'document'
def test_json_Message_Photo():
json_string = r'{"message_id":96,"from":{"id":109734,"first_name":"Fd","last_name":"Wd","username":"dd"},"chat":{"id":10734,"first_name":"Fd","last_name":"dd","username":"dd"},"date":1435478191,"photo":[{"file_id":"AgADBQADIagxG8YifgYv8yLSj76i-dd","file_size":615,"width":90,"height":67},{"file_id":"AgADBQADIagxG8YifgYv8yLSj76i-dd","file_size":10174,"width":320,"height":240},{"file_id":"dd-A_LsTIABFNx-FUOaEa_3AABAQABAg","file_size":53013,"width":759,"height":570}]}'
json_string = r'{"message_id":96,"from":{"id":109734,"first_name":"Fd","last_name":"Wd","username":"dd"},"chat":{"id":10734,"first_name":"Fd","type":"private","last_name":"dd","username":"dd"},"date":1435478191,"photo":[{"file_id":"AgADBQADIagxG8YifgYv8yLSj76i-dd","file_size":615,"width":90,"height":67},{"file_id":"AgADBQADIagxG8YifgYv8yLSj76i-dd","file_size":10174,"width":320,"height":240},{"file_id":"dd-A_LsTIABFNx-FUOaEa_3AABAQABAg","file_size":53013,"width":759,"height":570}]}'
msg = types.Message.de_json(json_string)
assert len(msg.photo) == 3
assert msg.content_type == 'photo'
def test_json_Message_Video():
json_string = r'{"message_id":101,"from":{"id":109734,"first_name":"dd","last_name":"dd","username":"dd"},"chat":{"id":109734,"first_name":"dd","last_name":"dd","username":"dd"},"date":1435481960,"video":{"duration":3,"caption":"","width":360,"height":640,"thumb":{"file_id":"AAQFABPiYnBjkDwMAAIC","file_size":1597,"width":50,"height":90},"file_id":"BAADBQADNifgb_TOPEKErGoQI","file_size":260699}}'
json_string = r'{"message_id":101,"from":{"id":109734,"first_name":"dd","last_name":"dd","username":"dd"},"chat":{"id":109734,"first_name":"dd","type":"private","last_name":"dd","username":"dd"},"date":1435481960,"video":{"duration":3,"caption":"","width":360,"height":640,"thumb":{"file_id":"AAQFABPiYnBjkDwMAAIC","file_size":1597,"width":50,"height":90},"file_id":"BAADBQADNifgb_TOPEKErGoQI","file_size":260699}}'
msg = types.Message.de_json(json_string)
assert msg.video
assert msg.video.duration == 3
@ -87,7 +87,7 @@ def test_json_Message_Video():
def test_json_Message_Location():
json_string = r'{"message_id":102,"from":{"id":108734,"first_name":"dd","last_name":"dd","username":"dd"},"chat":{"id":1089734,"first_name":"dd","last_name":"dd","username":"dd"},"date":1535482469,"location":{"longitude":127.479471,"latitude":26.090577}}'
json_string = r'{"message_id":102,"from":{"id":108734,"first_name":"dd","last_name":"dd","username":"dd"},"chat":{"id":1089734,"first_name":"dd","type":"private","last_name":"dd","username":"dd"},"date":1535482469,"location":{"longitude":127.479471,"latitude":26.090577}}'
msg = types.Message.de_json(json_string)
assert msg.location.latitude == 26.090577
assert msg.content_type == 'location'
@ -114,9 +114,16 @@ def test_json_voice():
assert voice.file_size == 10481
def test_json_update():
json_string = r'{"update_id":938203,"message":{"message_id":241,"from":{"id":9734,"first_name":"Fk","last_name":"Wg","username":"nir"},"chat":{"id":1111,"first_name":"Fk","last_name":"Wg","username":"oir"},"date":1441447009,"text":"HIHI"}}'
json_string = r'{"update_id":938203,"message":{"message_id":241,"from":{"id":9734,"first_name":"Fk","last_name":"Wg","username":"nir"},"chat":{"id":1111,"first_name":"Fk","type":"private","last_name":"Wg","username":"oir"},"date":1441447009,"text":"HIHI"}}'
update = types.Update.de_json(json_string)
assert update.update_id == 938203
assert update.message.message_id == 241
assert update.message.from_user.id == 9734
def test_json_chat():
json_string = r'{"id": -111111,"title": "Test Title","type": "group"}'
chat = types.Chat.de_json(json_string)
assert chat.id == -111111
assert chat.type == 'group'
assert chat.title == 'Test Title'