mirror of
https://github.com/eternnoir/pyTelegramBotAPI.git
synced 2023-08-10 21:12:57 +03:00
Compare commits
109 Commits
Author | SHA1 | Date | |
---|---|---|---|
003a92f466 | |||
d57aa04bfb | |||
9c2d279806 | |||
3109e35bb4 | |||
ea51b1e95e | |||
3799a1e99a | |||
ec8714ad3a | |||
bc54a5379c | |||
a7587057bf | |||
e9ba2fd8bb | |||
8203fa588f | |||
2e5250ec98 | |||
1f910745f1 | |||
f56da17741 | |||
a0d86977b0 | |||
82838e1d26 | |||
bb8bc7672a | |||
6e3e159109 | |||
87574a7613 | |||
52ebb5a1a7 | |||
da9ee5ffba | |||
0900acfae9 | |||
c6cf615722 | |||
5dc008a762 | |||
bbcd7aa9db | |||
51effdd9a1 | |||
0126ba82a5 | |||
6b0484b9db | |||
eab36a99e9 | |||
28357c8c33 | |||
6559f431b7 | |||
b5d054cf5f | |||
93b86307d9 | |||
f62d72e2a1 | |||
c4e624d999 | |||
fcb3d9b1b3 | |||
2534dc5925 | |||
ded0d257fc | |||
96686e5221 | |||
b522053e27 | |||
4e61bc3a8b | |||
487ede7c88 | |||
4658d2b8da | |||
75a18e5869 | |||
fab2b2d223 | |||
65c3ca58da | |||
a4d0b685b5 | |||
6cc80f25d7 | |||
0418818629 | |||
b9898bbdda | |||
00c9351f83 | |||
0a2216a22b | |||
438cfe4dbd | |||
640f398262 | |||
a9db217c64 | |||
5824d47590 | |||
0da192aec7 | |||
bd27645965 | |||
2c15cd0996 | |||
00d125a298 | |||
a548374a4d | |||
03e1aef70e | |||
ece7ca97e0 | |||
7a3fd30f6a | |||
1d99cc224f | |||
fa3ca84d24 | |||
42e6d84f13 | |||
27461c03af | |||
3be51390b1 | |||
afa88304d7 | |||
82e79b6ac6 | |||
746c71665e | |||
37c09406d0 | |||
36a3ce62c4 | |||
6dc8173176 | |||
00c2e9b51c | |||
29711e2425 | |||
75a5dd1492 | |||
3ae145f206 | |||
5fda52cf5d | |||
9ab906e60c | |||
698b4371e6 | |||
a803edd09b | |||
32a9e65ecc | |||
decad450d0 | |||
630a9a5b2c | |||
cdae65116b | |||
6832c33733 | |||
d15cb16bef | |||
81100f249c | |||
79ff9191f3 | |||
bdfb793e34 | |||
e811163b5f | |||
1eb9651894 | |||
309e55845c | |||
2bc5c1a500 | |||
7acad2d825 | |||
5120650774 | |||
c13f9a7f98 | |||
bab9b4077d | |||
47b9c1d3fb | |||
06ed637f2f | |||
7bf432170e | |||
8cd18945c5 | |||
cdd48c7aed | |||
513a85cad9 | |||
17f48916ad | |||
5b70980bda | |||
73487f96c4 |
@ -4,6 +4,7 @@ python:
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "pypy3"
|
||||
install: "pip install -r requirements.txt"
|
||||
script:
|
||||
|
24
README.md
24
README.md
@ -5,6 +5,7 @@
|
||||
[](https://pypi.python.org/pypi/pyTelegramBotAPI)
|
||||
[](https://pypi.python.org/pypi/pyTelegramBotAPI)
|
||||
[](https://travis-ci.org/eternnoir/pyTelegramBotAPI)
|
||||
[](https://pypi.org/project/pyTelegramBotAPI/)
|
||||
|
||||
* [Getting started.](#getting-started)
|
||||
* [Writing your first bot](#writing-your-first-bot)
|
||||
@ -39,7 +40,7 @@
|
||||
|
||||
## Getting started.
|
||||
|
||||
This API is tested with Python 2.6, Python 2.7, Python 3.4, Pypy and Pypy 3.
|
||||
This API is tested with Python Python 3.6-3.9 and Pypy 3.
|
||||
There are two ways to install the library:
|
||||
|
||||
* Installation using pip (a Python package manager)*:
|
||||
@ -504,7 +505,7 @@ bot.polling()
|
||||
### Using web hooks
|
||||
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.
|
||||
There are some examples using webhooks in the [examples/webhook_examples](examples/webhook_examples) directory.
|
||||
|
||||
### Logging
|
||||
|
||||
@ -539,8 +540,15 @@ apihelper.proxy = {'https':'socks5://userproxy:password@proxy_address:port'}
|
||||
|
||||
_Checking is in progress..._
|
||||
|
||||
✅ [Bot API 3.5](https://core.telegram.org/bots/api-changelog#november-17-2017) _- To be checked..._
|
||||
✅ [Bot API 4.5](https://core.telegram.org/bots/api-changelog#december-31-2019) _- To be checked..._
|
||||
|
||||
* ✔ [Bot API 4.4](https://core.telegram.org/bots/api-changelog#july-29-2019)
|
||||
* ✔ [Bot API 4.3](https://core.telegram.org/bots/api-changelog#may-31-2019)
|
||||
* ✔ [Bot API 4.2](https://core.telegram.org/bots/api-changelog#april-14-2019)
|
||||
* ➕ [Bot API 4.1](https://core.telegram.org/bots/api-changelog#august-27-2018) - No Passport support.
|
||||
* ➕ [Bot API 4.0](https://core.telegram.org/bots/api-changelog#july-26-2018) - No Passport support.
|
||||
* ✔ [Bot API 3.6](https://core.telegram.org/bots/api-changelog#february-13-2018)
|
||||
* ✔ [Bot API 3.5](https://core.telegram.org/bots/api-changelog#november-17-2017)
|
||||
* ✔ [Bot API 3.4](https://core.telegram.org/bots/api-changelog#october-11-2017)
|
||||
* ✔ [Bot API 3.3](https://core.telegram.org/bots/api-changelog#august-23-2017)
|
||||
* ✔ [Bot API 3.2](https://core.telegram.org/bots/api-changelog#july-21-2017)
|
||||
@ -655,5 +663,13 @@ Get help. Discuss. Chat.
|
||||
* [AdviceBook](https://t.me/adviceokbot) by [@barbax7](https://github.com/barbax7) - A Telegram Bot that allows you to receive random reading tips when you don't know which book to read.
|
||||
* [Blue_CC_Bot](https://t.me/Blue_CC_Bot) by [@Akash](https://github.com/BLUE-DEVIL1134) - A Telegram Bot Which Checks Your Given Credit Cards And Says Which Is A Real,Card And Which Is Fake.
|
||||
* [RandomInfoBot](https://t.me/RandomInfoBot) by [@Akash](https://github.com/BLUE-DEVIL1134) - A Telegram Bot Which Generates Random Information Of Humans Scraped From Over 13 Websites.
|
||||
* [TasksListsBot](https://t.me/TasksListsBot) ([source](https://github.com/Pablo-Davila/TasksListsBot)) by [@Pablo-Davila](https://github.com/Pablo-Davila) - A (tasks) lists manager bot for Telegram.
|
||||
* [MyElizaPsychologistBot](https://t.me/TasksListsBot) ([source](https://github.com/Pablo-Davila/MyElizaPsychologistBot)) by [@Pablo-Davila](https://github.com/Pablo-Davila) - An implementation of the famous Eliza psychologist chatbot.
|
||||
* [Evdembot](https://t.me/Evdembot) by Adem Kavak. A bot that informs you about everything you want.
|
||||
* [Frcstbot](https://t.me/frcstbot) ([source](https://github.com/Mrsqd/frcstbot_public)) by [Mrsqd](https://github.com/Mrsqd). A Telegram bot that will always be happy to show you the weather forecast.
|
||||
* [Bot Hour](https://t.me/roadtocode_bot) a little bot that say the time in different countries by [@diegop384](https://github.com/diegop384) [repo](https://github.com/diegop384/telegrambothour)
|
||||
* [moodforfood_bot](https://t.me/moodforfood_bot) This bot will provide you with a list of food place(s) near your current Telegram location, which you are prompted to share. The API for all this info is from https://foursquare.com/. by [@sophiamarani](https://github.com/sophiamarani)
|
||||
* [Donation with Amazon](https://t.me/donamazonbot) by [@barbax7](https://github.com/barbax7) This bot donates amazon advertising commissions to the non-profit organization chosen by the user.
|
||||
* [COVID-19 Galicia Bot](https://t.me/covid19_galicia_bot) by [@dgarcoe](https://github.com/dgarcoe) This bot provides daily data related to the COVID19 crisis in Galicia (Spain) obtained from official government sources.
|
||||
|
||||
Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh.
|
||||
Want to have your bot listed here? Just make a pull requet.
|
||||
|
@ -68,7 +68,7 @@ def process_sex_step(message):
|
||||
if (sex == u'Male') or (sex == u'Female'):
|
||||
user.sex = sex
|
||||
else:
|
||||
raise Exception()
|
||||
raise Exception("Unknown sex")
|
||||
bot.send_message(chat_id, 'Nice to meet you ' + user.name + '\n Age:' + str(user.age) + '\n Sex:' + user.sex)
|
||||
except Exception as e:
|
||||
bot.reply_to(message, 'oooops')
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Webhook examples using pyTelegramBotAPI
|
||||
|
||||
There are 4 examples in this directory using different libraries:
|
||||
There are 5 examples in this directory using different libraries:
|
||||
|
||||
* **Python (CPython):** *webhook_cpython_echo_bot.py*
|
||||
* **Pros:**
|
||||
@ -37,9 +37,18 @@ There are 4 examples in this directory using different libraries:
|
||||
* **Pros:**
|
||||
* It's a web application framework
|
||||
* Python 3 compatible
|
||||
* Asynchronous, excellent perfomance
|
||||
* Asynchronous, excellent performance
|
||||
* Utilizes new async/await syntax
|
||||
* **Cons:**
|
||||
* Requires Python 3.4.2+, don't work with Python 2
|
||||
|
||||
*Latest update of this document: 2017-01-30*
|
||||
* **Twisted (20.3.0):** *webhook_twisted_echo_bot.py*
|
||||
* **Pros:**
|
||||
* Asynchronous event-driven networking engine
|
||||
* Very high performance
|
||||
* Built-in support for many internet protocols
|
||||
* **Cons:**
|
||||
* Twisted is low-level, which may be good or bad depending on use case
|
||||
* Considerable learning curve - reading docs is a must.
|
||||
|
||||
*Latest update of this document: 2020-12-17*
|
||||
|
79
examples/webhook_examples/webhook_twisted_echo_bot.py
Normal file
79
examples/webhook_examples/webhook_twisted_echo_bot.py
Normal file
@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This is an example echo bot using webhook with Twisted network framework.
|
||||
# Updates are received with Twisted web server and processed in reactor thread pool.
|
||||
# Relevant docs:
|
||||
# https://twistedmatrix.com/documents/current/core/howto/reactor-basics.html
|
||||
# https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html
|
||||
|
||||
import logging
|
||||
import telebot
|
||||
import json
|
||||
from twisted.internet import ssl, reactor
|
||||
from twisted.web.resource import Resource, ErrorPage
|
||||
from twisted.web.server import Site, Request
|
||||
|
||||
API_TOKEN = '<api_token>'
|
||||
|
||||
WEBHOOK_HOST = '<ip or hostname>'
|
||||
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://{}:{}".format(WEBHOOK_HOST, WEBHOOK_PORT)
|
||||
WEBHOOK_URL_PATH = "/{}/".format(API_TOKEN)
|
||||
|
||||
logger = telebot.logger
|
||||
telebot.logger.setLevel(logging.INFO)
|
||||
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.\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'))
|
||||
|
||||
|
||||
# Process webhook calls
|
||||
class WebhookHandler(Resource):
|
||||
isLeaf = True
|
||||
def render_POST(self, request: Request):
|
||||
request_body_dict = json.load(request.content)
|
||||
update = telebot.types.Update.de_json(request_body_dict)
|
||||
reactor.callInThread(lambda: bot.process_new_updates([update]))
|
||||
return b''
|
||||
|
||||
|
||||
root = ErrorPage(403, 'Forbidden', '')
|
||||
root.putChild(API_TOKEN.encode(), WebhookHandler())
|
||||
site = Site(root)
|
||||
sslcontext = ssl.DefaultOpenSSLContextFactory(WEBHOOK_SSL_PRIV, WEBHOOK_SSL_CERT)
|
||||
reactor.listenSSL(8443, site, sslcontext)
|
||||
reactor.run()
|
@ -1,5 +1,4 @@
|
||||
py==1.4.29
|
||||
pytest==3.0.2
|
||||
requests==2.20.0
|
||||
six==1.9.0
|
||||
wheel==0.24.0
|
||||
|
2
setup.py
2
setup.py
@ -22,7 +22,7 @@ setup(name='pyTelegramBotAPI',
|
||||
packages=['telebot'],
|
||||
license='GPL2',
|
||||
keywords='telegram bot api tools',
|
||||
install_requires=['requests', 'six'],
|
||||
install_requires=['requests'],
|
||||
extras_require={
|
||||
'json': 'ujson',
|
||||
'redis': 'redis>=3.4.1'
|
||||
|
@ -6,8 +6,7 @@ import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
import six
|
||||
import traceback
|
||||
|
||||
logger = logging.getLogger('TeleBot')
|
||||
formatter = logging.Formatter(
|
||||
@ -42,6 +41,16 @@ class Handler:
|
||||
return getattr(self, item)
|
||||
|
||||
|
||||
class ExceptionHandler:
|
||||
"""
|
||||
Class for handling exceptions while Polling
|
||||
"""
|
||||
|
||||
# noinspection PyMethodMayBeStatic,PyUnusedLocal
|
||||
def handle(self, exception):
|
||||
return False
|
||||
|
||||
|
||||
class TeleBot:
|
||||
""" This is TeleBot Class
|
||||
Methods:
|
||||
@ -86,7 +95,7 @@ class TeleBot:
|
||||
|
||||
def __init__(
|
||||
self, token, parse_mode=None, threaded=True, skip_pending=False, num_threads=2,
|
||||
next_step_backend=None, reply_backend=None
|
||||
next_step_backend=None, reply_backend=None, exception_handler=None, last_update_id=0
|
||||
):
|
||||
"""
|
||||
:param token: bot API token
|
||||
@ -100,7 +109,7 @@ class TeleBot:
|
||||
self.skip_pending = skip_pending
|
||||
|
||||
self.__stop_polling = threading.Event()
|
||||
self.last_update_id = 0
|
||||
self.last_update_id = last_update_id
|
||||
self.exc_info = None
|
||||
|
||||
self.next_step_backend = next_step_backend
|
||||
@ -111,6 +120,8 @@ class TeleBot:
|
||||
if not self.reply_backend:
|
||||
self.reply_backend = MemoryHandlerBackend()
|
||||
|
||||
self.exception_handler = exception_handler
|
||||
|
||||
self.message_handlers = []
|
||||
self.edited_message_handlers = []
|
||||
self.channel_post_handlers = []
|
||||
@ -123,20 +134,20 @@ class TeleBot:
|
||||
self.poll_handlers = []
|
||||
self.poll_answer_handlers = []
|
||||
|
||||
self.typed_middleware_handlers = {
|
||||
'message': [],
|
||||
'edited_message': [],
|
||||
'channel_post': [],
|
||||
'edited_channel_post': [],
|
||||
'inline_query': [],
|
||||
'chosen_inline_result': [],
|
||||
'callback_query': [],
|
||||
'shipping_query': [],
|
||||
'pre_checkout_query': [],
|
||||
'poll': [],
|
||||
}
|
||||
|
||||
self.default_middleware_handlers = []
|
||||
if apihelper.ENABLE_MIDDLEWARE:
|
||||
self.typed_middleware_handlers = {
|
||||
'message': [],
|
||||
'edited_message': [],
|
||||
'channel_post': [],
|
||||
'edited_channel_post': [],
|
||||
'inline_query': [],
|
||||
'chosen_inline_result': [],
|
||||
'callback_query': [],
|
||||
'shipping_query': [],
|
||||
'pre_checkout_query': [],
|
||||
'poll': [],
|
||||
}
|
||||
self.default_middleware_handlers = []
|
||||
|
||||
self.threaded = threaded
|
||||
if self.threaded:
|
||||
@ -226,33 +237,59 @@ class TeleBot:
|
||||
"""
|
||||
self.reply_backend.load_handlers(filename, del_file_after_loading)
|
||||
|
||||
def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None):
|
||||
return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates)
|
||||
def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None,
|
||||
drop_pending_updates = None, timeout=None):
|
||||
"""
|
||||
Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an
|
||||
update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized Update.
|
||||
In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns True on success.
|
||||
|
||||
def delete_webhook(self):
|
||||
:param url: HTTPS url to send updates to. Use an empty string to remove webhook integration
|
||||
:param certificate: Upload your public key certificate so that the root certificate in use can be checked. See our self-signed guide for details.
|
||||
:param max_connections: Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput.
|
||||
:param allowed_updates: A JSON-serialized list of the update types you want your bot to receive. For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. See Update for a complete list of available update types. Specify an empty list to receive all updates regardless of type (default). If not specified, the previous setting will be used.
|
||||
:param ip_address: The fixed IP address which will be used to send webhook requests instead of the IP address resolved through DNS
|
||||
:param drop_pending_updates: Pass True to drop all pending updates
|
||||
:param timeout: Integer. Request connection timeout
|
||||
:return:
|
||||
"""
|
||||
return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates, ip_address, drop_pending_updates, timeout)
|
||||
|
||||
def delete_webhook(self, drop_pending_updates=None, timeout=None):
|
||||
"""
|
||||
Use this method to remove webhook integration if you decide to switch back to getUpdates.
|
||||
|
||||
:param drop_pending_updates: Pass True to drop all pending updates
|
||||
:param timeout: Integer. Request connection timeout
|
||||
:return: bool
|
||||
"""
|
||||
return apihelper.delete_webhook(self.token)
|
||||
return apihelper.delete_webhook(self.token, drop_pending_updates, timeout)
|
||||
|
||||
def get_webhook_info(self):
|
||||
result = apihelper.get_webhook_info(self.token)
|
||||
def get_webhook_info(self, timeout=None):
|
||||
"""
|
||||
Use this method to get current webhook status. Requires no parameters.
|
||||
If the bot is using getUpdates, will return an object with the url field empty.
|
||||
|
||||
:param timeout: Integer. Request connection timeout
|
||||
:return: On success, returns a WebhookInfo object.
|
||||
"""
|
||||
result = apihelper.get_webhook_info(self.token, timeout)
|
||||
return types.WebhookInfo.de_json(result)
|
||||
|
||||
def remove_webhook(self):
|
||||
return self.set_webhook() # No params resets webhook
|
||||
|
||||
def get_updates(self, offset=None, limit=None, timeout=20, allowed_updates=None):
|
||||
def get_updates(self, offset=None, limit=None, timeout=20, allowed_updates=None, long_polling_timeout = 20):
|
||||
"""
|
||||
Use this method to receive incoming updates using long polling (wiki). An Array of Update objects is returned.
|
||||
:param allowed_updates: Array of string. List the types of updates you want your bot to receive.
|
||||
:param offset: Integer. Identifier of the first update to be returned.
|
||||
:param limit: Integer. Limits the number of updates to be retrieved.
|
||||
:param timeout: Integer. Timeout in seconds for long polling.
|
||||
:param timeout: Integer. Request connection timeout
|
||||
:param long_polling_timeout. Timeout in seconds for long polling.
|
||||
:return: array of Updates
|
||||
"""
|
||||
json_updates = apihelper.get_updates(self.token, offset, limit, timeout, allowed_updates)
|
||||
json_updates = apihelper.get_updates(self.token, offset, limit, timeout, allowed_updates, long_polling_timeout)
|
||||
ret = []
|
||||
for ju in json_updates:
|
||||
ret.append(types.Update.de_json(ju))
|
||||
@ -264,16 +301,16 @@ class TeleBot:
|
||||
:return: total updates skipped
|
||||
"""
|
||||
total = 0
|
||||
updates = self.get_updates(offset=self.last_update_id, timeout=1)
|
||||
updates = self.get_updates(offset=self.last_update_id, long_polling_timeout=1)
|
||||
while updates:
|
||||
total += len(updates)
|
||||
for update in updates:
|
||||
if update.update_id > self.last_update_id:
|
||||
self.last_update_id = update.update_id
|
||||
updates = self.get_updates(offset=self.last_update_id + 1, timeout=1)
|
||||
updates = self.get_updates(offset=self.last_update_id + 1, long_polling_timeout=1)
|
||||
return total
|
||||
|
||||
def __retrieve_updates(self, timeout=20):
|
||||
def __retrieve_updates(self, timeout=20, long_polling_timeout=20):
|
||||
"""
|
||||
Retrieves any updates from the Telegram API.
|
||||
Registered listeners and applicable message handlers will be notified when a new message arrives.
|
||||
@ -282,21 +319,26 @@ class TeleBot:
|
||||
if self.skip_pending:
|
||||
logger.debug('Skipped {0} pending messages'.format(self.__skip_updates()))
|
||||
self.skip_pending = False
|
||||
updates = self.get_updates(offset=(self.last_update_id + 1), timeout=timeout)
|
||||
updates = self.get_updates(offset=(self.last_update_id + 1), timeout=timeout, long_polling_timeout = long_polling_timeout)
|
||||
self.process_new_updates(updates)
|
||||
|
||||
def process_new_updates(self, updates):
|
||||
new_messages = []
|
||||
new_edited_messages = []
|
||||
new_channel_posts = []
|
||||
new_edited_channel_posts = []
|
||||
new_inline_querys = []
|
||||
new_chosen_inline_results = []
|
||||
new_callback_querys = []
|
||||
new_shipping_querys = []
|
||||
new_pre_checkout_querys = []
|
||||
new_polls = []
|
||||
new_poll_answers = []
|
||||
upd_count = len(updates)
|
||||
logger.debug('Received {0} new updates'.format(upd_count))
|
||||
if (upd_count == 0):
|
||||
return
|
||||
|
||||
new_messages = None
|
||||
new_edited_messages = None
|
||||
new_channel_posts = None
|
||||
new_edited_channel_posts = None
|
||||
new_inline_queries = None
|
||||
new_chosen_inline_results = None
|
||||
new_callback_queries = None
|
||||
new_shipping_queries = None
|
||||
new_pre_checkout_queries = None
|
||||
new_polls = None
|
||||
new_poll_answers = None
|
||||
|
||||
for update in updates:
|
||||
if apihelper.ENABLE_MIDDLEWARE:
|
||||
@ -305,50 +347,60 @@ class TeleBot:
|
||||
if update.update_id > self.last_update_id:
|
||||
self.last_update_id = update.update_id
|
||||
if update.message:
|
||||
if new_messages is None: new_messages = []
|
||||
new_messages.append(update.message)
|
||||
if update.edited_message:
|
||||
if new_edited_messages is None: new_edited_messages = []
|
||||
new_edited_messages.append(update.edited_message)
|
||||
if update.channel_post:
|
||||
if new_channel_posts is None: new_channel_posts = []
|
||||
new_channel_posts.append(update.channel_post)
|
||||
if update.edited_channel_post:
|
||||
if new_edited_channel_posts is None: new_edited_channel_posts = []
|
||||
new_edited_channel_posts.append(update.edited_channel_post)
|
||||
if update.inline_query:
|
||||
new_inline_querys.append(update.inline_query)
|
||||
if new_inline_queries is None: new_inline_queries = []
|
||||
new_inline_queries.append(update.inline_query)
|
||||
if update.chosen_inline_result:
|
||||
if new_chosen_inline_results is None: new_chosen_inline_results = []
|
||||
new_chosen_inline_results.append(update.chosen_inline_result)
|
||||
if update.callback_query:
|
||||
new_callback_querys.append(update.callback_query)
|
||||
if new_callback_queries is None: new_callback_queries = []
|
||||
new_callback_queries.append(update.callback_query)
|
||||
if update.shipping_query:
|
||||
new_shipping_querys.append(update.shipping_query)
|
||||
if new_shipping_queries is None: new_shipping_queries = []
|
||||
new_shipping_queries.append(update.shipping_query)
|
||||
if update.pre_checkout_query:
|
||||
new_pre_checkout_querys.append(update.pre_checkout_query)
|
||||
if new_pre_checkout_queries is None: new_pre_checkout_queries = []
|
||||
new_pre_checkout_queries.append(update.pre_checkout_query)
|
||||
if update.poll:
|
||||
if new_polls is None: new_polls = []
|
||||
new_polls.append(update.poll)
|
||||
if update.poll_answer:
|
||||
if new_poll_answers is None: new_poll_answers = []
|
||||
new_poll_answers.append(update.poll_answer)
|
||||
|
||||
logger.debug('Received {0} new updates'.format(len(updates)))
|
||||
if len(new_messages) > 0:
|
||||
if new_messages:
|
||||
self.process_new_messages(new_messages)
|
||||
if len(new_edited_messages) > 0:
|
||||
if new_edited_messages:
|
||||
self.process_new_edited_messages(new_edited_messages)
|
||||
if len(new_channel_posts) > 0:
|
||||
if new_channel_posts:
|
||||
self.process_new_channel_posts(new_channel_posts)
|
||||
if len(new_edited_channel_posts) > 0:
|
||||
if new_edited_channel_posts:
|
||||
self.process_new_edited_channel_posts(new_edited_channel_posts)
|
||||
if len(new_inline_querys) > 0:
|
||||
self.process_new_inline_query(new_inline_querys)
|
||||
if len(new_chosen_inline_results) > 0:
|
||||
if new_inline_queries:
|
||||
self.process_new_inline_query(new_inline_queries)
|
||||
if new_chosen_inline_results:
|
||||
self.process_new_chosen_inline_query(new_chosen_inline_results)
|
||||
if len(new_callback_querys) > 0:
|
||||
self.process_new_callback_query(new_callback_querys)
|
||||
if len(new_shipping_querys) > 0:
|
||||
self.process_new_shipping_query(new_shipping_querys)
|
||||
if len(new_pre_checkout_querys) > 0:
|
||||
self.process_new_pre_checkout_query(new_pre_checkout_querys)
|
||||
if len(new_polls) > 0:
|
||||
if new_callback_queries:
|
||||
self.process_new_callback_query(new_callback_queries)
|
||||
if new_shipping_queries:
|
||||
self.process_new_shipping_query(new_shipping_queries)
|
||||
if new_pre_checkout_queries:
|
||||
self.process_new_pre_checkout_query(new_pre_checkout_queries)
|
||||
if new_polls:
|
||||
self.process_new_poll(new_polls)
|
||||
if len(new_poll_answers) > 0:
|
||||
if new_poll_answers:
|
||||
self.process_new_poll_answer(new_poll_answers)
|
||||
|
||||
def process_new_messages(self, new_messages):
|
||||
@ -398,19 +450,24 @@ class TeleBot:
|
||||
default_middleware_handler(self, update)
|
||||
|
||||
def __notify_update(self, new_messages):
|
||||
if len(self.update_listener) == 0:
|
||||
return
|
||||
for listener in self.update_listener:
|
||||
self._exec_task(listener, new_messages)
|
||||
|
||||
def infinity_polling(self, timeout=20, *args, **kwargs):
|
||||
def infinity_polling(self, timeout=20, long_polling_timeout=20, *args, **kwargs):
|
||||
while not self.__stop_polling.is_set():
|
||||
try:
|
||||
self.polling(timeout=timeout, *args, **kwargs)
|
||||
except Exception:
|
||||
time.sleep(timeout)
|
||||
pass
|
||||
self.polling(none_stop=True, timeout=timeout, long_polling_timeout=long_polling_timeout, *args, **kwargs)
|
||||
except Exception as e:
|
||||
logger.error("Infinity polling exception: %s", str(e))
|
||||
logger.debug("Exception traceback:\n%s", traceback.format_exc())
|
||||
time.sleep(3)
|
||||
continue
|
||||
logger.info("Infinity polling: polling exited")
|
||||
logger.info("Break infinity polling")
|
||||
|
||||
def polling(self, none_stop=False, interval=0, timeout=20):
|
||||
def polling(self, none_stop=False, interval=0, timeout=20, long_polling_timeout=20):
|
||||
"""
|
||||
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.
|
||||
@ -420,15 +477,16 @@ class TeleBot:
|
||||
Always get updates.
|
||||
:param interval:
|
||||
:param none_stop: Do not stop polling when an ApiException occurs.
|
||||
:param timeout: Timeout in seconds for long polling.
|
||||
:param timeout: Integer. Request connection timeout
|
||||
:param long_polling_timeout. Timeout in seconds for long polling.
|
||||
:return:
|
||||
"""
|
||||
if self.threaded:
|
||||
self.__threaded_polling(none_stop, interval, timeout)
|
||||
self.__threaded_polling(none_stop, interval, timeout, long_polling_timeout)
|
||||
else:
|
||||
self.__non_threaded_polling(none_stop, interval, timeout)
|
||||
self.__non_threaded_polling(none_stop, interval, timeout, long_polling_timeout)
|
||||
|
||||
def __threaded_polling(self, none_stop=False, interval=0, timeout=3):
|
||||
def __threaded_polling(self, non_stop=False, interval=0, timeout = None, long_polling_timeout = None):
|
||||
logger.info('Started polling.')
|
||||
self.__stop_polling.clear()
|
||||
error_interval = 0.25
|
||||
@ -443,55 +501,96 @@ class TeleBot:
|
||||
while not self.__stop_polling.wait(interval):
|
||||
or_event.clear()
|
||||
try:
|
||||
polling_thread.put(self.__retrieve_updates, timeout)
|
||||
|
||||
polling_thread.put(self.__retrieve_updates, timeout, long_polling_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 = 0.25
|
||||
except apihelper.ApiException as e:
|
||||
logger.error(e)
|
||||
if not none_stop:
|
||||
self.__stop_polling.set()
|
||||
logger.info("Exception occurred. Stopping.")
|
||||
if self.exception_handler is not None:
|
||||
handled = self.exception_handler.handle(e)
|
||||
else:
|
||||
polling_thread.clear_exceptions()
|
||||
self.worker_pool.clear_exceptions()
|
||||
logger.info("Waiting for {0} seconds until retry".format(error_interval))
|
||||
handled = False
|
||||
if not handled:
|
||||
logger.error(e)
|
||||
if not non_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
|
||||
else:
|
||||
# polling_thread.clear_exceptions()
|
||||
# self.worker_pool.clear_exceptions()
|
||||
time.sleep(error_interval)
|
||||
error_interval *= 2
|
||||
polling_thread.clear_exceptions() #*
|
||||
self.worker_pool.clear_exceptions() #*
|
||||
except KeyboardInterrupt:
|
||||
logger.info("KeyboardInterrupt received.")
|
||||
self.__stop_polling.set()
|
||||
break
|
||||
except Exception as e:
|
||||
if self.exception_handler is not None:
|
||||
handled = self.exception_handler.handle(e)
|
||||
else:
|
||||
handled = False
|
||||
if not handled:
|
||||
polling_thread.stop()
|
||||
polling_thread.clear_exceptions() #*
|
||||
self.worker_pool.clear_exceptions() #*
|
||||
raise e
|
||||
else:
|
||||
polling_thread.clear_exceptions()
|
||||
self.worker_pool.clear_exceptions()
|
||||
time.sleep(error_interval)
|
||||
|
||||
polling_thread.stop()
|
||||
polling_thread.clear_exceptions() #*
|
||||
self.worker_pool.clear_exceptions() #*
|
||||
logger.info('Stopped polling.')
|
||||
|
||||
def __non_threaded_polling(self, none_stop=False, interval=0, timeout=3):
|
||||
def __non_threaded_polling(self, non_stop=False, interval=0, timeout = None, long_polling_timeout = None):
|
||||
logger.info('Started polling.')
|
||||
self.__stop_polling.clear()
|
||||
error_interval = 0.25
|
||||
|
||||
while not self.__stop_polling.wait(interval):
|
||||
try:
|
||||
self.__retrieve_updates(timeout)
|
||||
self.__retrieve_updates(timeout, long_polling_timeout)
|
||||
error_interval = 0.25
|
||||
except apihelper.ApiException as e:
|
||||
logger.error(e)
|
||||
if not none_stop:
|
||||
self.__stop_polling.set()
|
||||
logger.info("Exception occurred. Stopping.")
|
||||
if self.exception_handler is not None:
|
||||
handled = self.exception_handler.handle(e)
|
||||
else:
|
||||
handled = False
|
||||
|
||||
if not handled:
|
||||
logger.error(e)
|
||||
if not non_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
|
||||
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
|
||||
except Exception as e:
|
||||
if self.exception_handler is not None:
|
||||
handled = self.exception_handler.handle(e)
|
||||
else:
|
||||
handled = False
|
||||
if not handled:
|
||||
raise e
|
||||
else:
|
||||
time.sleep(error_interval)
|
||||
|
||||
logger.info('Stopped polling.')
|
||||
|
||||
@ -621,8 +720,8 @@ class TeleBot:
|
||||
"""
|
||||
Use this method to send text messages.
|
||||
|
||||
Warning: Do not send more than about 5000 characters each message, otherwise you'll risk an HTTP 414 error.
|
||||
If you must send more than 5000 characters, use the split_string function in apihelper.py.
|
||||
Warning: Do not send more than about 4000 characters each message, otherwise you'll risk an HTTP 414 error.
|
||||
If you must send more than 4000 characters, use the split_string function in apihelper.py.
|
||||
|
||||
:param chat_id:
|
||||
:param text:
|
||||
@ -634,7 +733,7 @@ class TeleBot:
|
||||
:param timeout:
|
||||
:return: API reply.
|
||||
"""
|
||||
parse_mode = self.parse_mode if not parse_mode else parse_mode
|
||||
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
|
||||
|
||||
return types.Message.de_json(
|
||||
apihelper.send_message(self.token, chat_id, text, disable_web_page_preview, reply_to_message_id,
|
||||
@ -647,6 +746,7 @@ class TeleBot:
|
||||
:param chat_id: which chat to forward
|
||||
:param from_chat_id: which chat message from
|
||||
:param message_id: message id
|
||||
:param timeout:
|
||||
:return: API reply.
|
||||
"""
|
||||
return types.Message.de_json(
|
||||
@ -657,6 +757,7 @@ class TeleBot:
|
||||
Use this method to delete message. Returns True on success.
|
||||
:param chat_id: in which chat to delete
|
||||
:param message_id: which message to delete
|
||||
:param timeout:
|
||||
:return: API reply.
|
||||
"""
|
||||
return apihelper.delete_message(self.token, chat_id, message_id, timeout)
|
||||
@ -672,6 +773,7 @@ class TeleBot:
|
||||
:param disable_notification:
|
||||
:param reply_to_message_id:
|
||||
:param reply_markup:
|
||||
:param timeout:
|
||||
:return: Message
|
||||
"""
|
||||
return types.Message.de_json(
|
||||
@ -691,9 +793,10 @@ class TeleBot:
|
||||
:param parse_mode
|
||||
:param reply_to_message_id:
|
||||
:param reply_markup:
|
||||
:param timeout:
|
||||
:return: API reply.
|
||||
"""
|
||||
parse_mode = self.parse_mode if not parse_mode else parse_mode
|
||||
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
|
||||
|
||||
return types.Message.de_json(
|
||||
apihelper.send_photo(self.token, chat_id, photo, caption, reply_to_message_id, reply_markup,
|
||||
@ -718,6 +821,8 @@ class TeleBot:
|
||||
:param thumb:
|
||||
:return: Message
|
||||
"""
|
||||
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
|
||||
|
||||
return types.Message.de_json(
|
||||
apihelper.send_audio(self.token, chat_id, audio, caption, duration, performer, title, reply_to_message_id,
|
||||
reply_markup, parse_mode, disable_notification, timeout, thumb))
|
||||
@ -737,7 +842,7 @@ class TeleBot:
|
||||
:param timeout:
|
||||
:return: Message
|
||||
"""
|
||||
parse_mode = self.parse_mode if not parse_mode else parse_mode
|
||||
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
|
||||
|
||||
return types.Message.de_json(
|
||||
apihelper.send_voice(self.token, chat_id, voice, caption, duration, reply_to_message_id, reply_markup,
|
||||
@ -758,7 +863,7 @@ class TeleBot:
|
||||
:param thumb: InputFile or String : Thumbnail of the file sent
|
||||
:return: API reply.
|
||||
"""
|
||||
parse_mode = self.parse_mode if not parse_mode else parse_mode
|
||||
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
|
||||
|
||||
return types.Message.de_json(
|
||||
apihelper.send_data(self.token, chat_id, data, 'document', reply_to_message_id, reply_markup,
|
||||
@ -801,7 +906,7 @@ class TeleBot:
|
||||
:param height:
|
||||
:return:
|
||||
"""
|
||||
parse_mode = self.parse_mode if not parse_mode else parse_mode
|
||||
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
|
||||
|
||||
return types.Message.de_json(
|
||||
apihelper.send_video(self.token, chat_id, data, duration, caption, reply_to_message_id, reply_markup,
|
||||
@ -825,7 +930,7 @@ class TeleBot:
|
||||
:param thumb: InputFile or String : Thumbnail of the file sent
|
||||
:return:
|
||||
"""
|
||||
parse_mode = self.parse_mode if not parse_mode else parse_mode
|
||||
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
|
||||
|
||||
return types.Message.de_json(
|
||||
apihelper.send_animation(self.token, chat_id, animation, duration, caption, reply_to_message_id, reply_markup,
|
||||
@ -860,6 +965,7 @@ class TeleBot:
|
||||
:param media:
|
||||
:param disable_notification:
|
||||
:param reply_to_message_id:
|
||||
:param timeout:
|
||||
:return:
|
||||
"""
|
||||
result = apihelper.send_media_group(
|
||||
@ -881,6 +987,7 @@ class TeleBot:
|
||||
:param reply_to_message_id:
|
||||
:param reply_markup:
|
||||
:param disable_notification:
|
||||
:param timeout:
|
||||
:return: API reply.
|
||||
"""
|
||||
return types.Message.de_json(
|
||||
@ -898,6 +1005,7 @@ class TeleBot:
|
||||
:param message_id:
|
||||
:param inline_message_id:
|
||||
:param reply_markup:
|
||||
:param timeout:
|
||||
:return:
|
||||
"""
|
||||
return types.Message.de_json(
|
||||
@ -915,6 +1023,7 @@ class TeleBot:
|
||||
:param message_id:
|
||||
:param inline_message_id:
|
||||
:param reply_markup:
|
||||
:param timeout:
|
||||
:return:
|
||||
"""
|
||||
return types.Message.de_json(
|
||||
@ -922,8 +1031,8 @@ class TeleBot:
|
||||
self.token, chat_id, message_id, inline_message_id, reply_markup, timeout))
|
||||
|
||||
def send_venue(
|
||||
self, chat_id, latitude, longitude, title, address, foursquare_id=None, disable_notification=None,
|
||||
reply_to_message_id=None, reply_markup=None, timeout=None):
|
||||
self, chat_id, latitude, longitude, title, address, foursquare_id=None, foursquare_type=None,
|
||||
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None):
|
||||
"""
|
||||
Use this method to send information about a venue.
|
||||
:param chat_id: Integer or String : Unique identifier for the target chat or username of the target channel
|
||||
@ -932,25 +1041,26 @@ class TeleBot:
|
||||
:param title: String : Name of the venue
|
||||
:param address: String : Address of the venue
|
||||
:param foursquare_id: String : Foursquare identifier of the venue
|
||||
:param foursquare_type: Foursquare type of the venue, if known. (For example, “arts_entertainment/default”, “arts_entertainment/aquarium” or “food/icecream”.)
|
||||
:param disable_notification:
|
||||
:param reply_to_message_id:
|
||||
:param reply_markup:
|
||||
:param timeout:
|
||||
:return:
|
||||
"""
|
||||
return types.Message.de_json(
|
||||
apihelper.send_venue(
|
||||
self.token, chat_id, latitude, longitude, title, address, foursquare_id,
|
||||
self.token, chat_id, latitude, longitude, title, address, foursquare_id, foursquare_type,
|
||||
disable_notification, reply_to_message_id, reply_markup, timeout)
|
||||
)
|
||||
|
||||
def send_contact(
|
||||
self, chat_id, phone_number, first_name,
|
||||
last_name=None, disable_notification=None,
|
||||
reply_to_message_id=None, reply_markup=None, timeout=None):
|
||||
self, chat_id, phone_number, first_name, last_name=None, vcard=None,
|
||||
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None):
|
||||
return types.Message.de_json(
|
||||
apihelper.send_contact(
|
||||
self.token, chat_id, phone_number, first_name, last_name, disable_notification,
|
||||
reply_to_message_id, reply_markup, timeout)
|
||||
self.token, chat_id, phone_number, first_name, last_name, vcard,
|
||||
disable_notification, reply_to_message_id, reply_markup, timeout)
|
||||
)
|
||||
|
||||
def send_chat_action(self, chat_id, action, timeout=None):
|
||||
@ -961,6 +1071,7 @@ class TeleBot:
|
||||
:param chat_id:
|
||||
:param action: One of the following strings: 'typing', 'upload_photo', 'record_video', 'upload_video',
|
||||
'record_audio', 'upload_audio', 'upload_document', 'find_location', 'record_video_note', 'upload_video_note'.
|
||||
:param timeout:
|
||||
:return: API reply. :type: boolean
|
||||
"""
|
||||
return apihelper.send_chat_action(self.token, chat_id, action, timeout)
|
||||
@ -976,14 +1087,20 @@ class TeleBot:
|
||||
"""
|
||||
return apihelper.kick_chat_member(self.token, chat_id, user_id, until_date)
|
||||
|
||||
def unban_chat_member(self, chat_id, user_id):
|
||||
def unban_chat_member(self, chat_id, user_id, only_if_banned = False):
|
||||
"""
|
||||
Removes member from the ban
|
||||
:param chat_id:
|
||||
:param user_id:
|
||||
:return:
|
||||
Use this method to unban a previously kicked user in a supergroup or channel.
|
||||
The user will not return to the group or channel automatically, but will be able to join via link, etc.
|
||||
The bot must be an administrator for this to work. By default, this method guarantees that after the call
|
||||
the user is not a member of the chat, but will be able to join it. So if the user is a member of the chat
|
||||
they will also be removed from the chat. If you don't want this, use the parameter only_if_banned.
|
||||
|
||||
:param chat_id: Unique identifier for the target group or username of the target supergroup or channel (in the format @username)
|
||||
:param user_id: Unique identifier of the target user
|
||||
:param only_if_banned: Do nothing if the user is not banned
|
||||
:return: True on success
|
||||
"""
|
||||
return apihelper.unban_chat_member(self.token, chat_id, user_id)
|
||||
return apihelper.unban_chat_member(self.token, chat_id, user_id, only_if_banned)
|
||||
|
||||
def restrict_chat_member(
|
||||
self, chat_id, user_id, until_date=None,
|
||||
@ -995,7 +1112,7 @@ class TeleBot:
|
||||
Use this method to restrict a user in a supergroup.
|
||||
The bot must be an administrator in the supergroup for this to work and must have
|
||||
the appropriate admin rights. Pass True for all boolean parameters to lift restrictions from a user.
|
||||
Returns True on success.
|
||||
|
||||
:param chat_id: Int or String : Unique identifier for the target group or username of the target supergroup
|
||||
or channel (in the format @channelusername)
|
||||
:param user_id: Int : Unique identifier of the target user
|
||||
@ -1014,7 +1131,7 @@ class TeleBot:
|
||||
:param can_invite_users: Pass True, if the user is allowed to invite new users to the chat,
|
||||
implies can_invite_users
|
||||
:param can_pin_messages: Pass True, if the user is allowed to pin messages. Ignored in public supergroups
|
||||
:return: types.Message
|
||||
:return: True on success
|
||||
"""
|
||||
return apihelper.restrict_chat_member(
|
||||
self.token, chat_id, user_id, until_date,
|
||||
@ -1029,7 +1146,8 @@ class TeleBot:
|
||||
"""
|
||||
Use this method to promote or demote a user in a supergroup or a channel. The bot must be an administrator
|
||||
in the chat for this to work and must have the appropriate admin rights.
|
||||
Pass False for all boolean parameters to demote a user. Returns True on success.
|
||||
Pass False for all boolean parameters to demote a user.
|
||||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target channel (
|
||||
in the format @channelusername)
|
||||
:param user_id: Int : Unique identifier of the target user
|
||||
@ -1043,7 +1161,7 @@ class TeleBot:
|
||||
:param can_promote_members: Bool: Pass True, if the administrator can add new administrators with a subset
|
||||
of his own privileges or demote administrators that he has promoted, directly or indirectly
|
||||
(promoted by administrators that were appointed by him)
|
||||
:return:
|
||||
:return: True on success.
|
||||
"""
|
||||
return apihelper.promote_chat_member(self.token, chat_id, user_id, can_change_info, can_post_messages,
|
||||
can_edit_messages, can_delete_messages, can_invite_users,
|
||||
@ -1052,27 +1170,27 @@ class TeleBot:
|
||||
def set_chat_administrator_custom_title(self, chat_id, user_id, custom_title):
|
||||
"""
|
||||
Use this method to set a custom title for an administrator
|
||||
in a supergroup promoted by the bot.
|
||||
Returns True on success.
|
||||
in a supergroup promoted by the bot.
|
||||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target supergroup
|
||||
(in the format @supergroupusername)
|
||||
:param user_id: Unique identifier of the target user
|
||||
:param custom_title: New custom title for the administrator;
|
||||
0-16 characters, emoji are not allowed
|
||||
:return:
|
||||
:return: True on success.
|
||||
"""
|
||||
return apihelper.set_chat_administrator_custom_title(self.token, chat_id, user_id, custom_title)
|
||||
|
||||
|
||||
def set_chat_permissions(self, chat_id, permissions):
|
||||
"""
|
||||
Use this method to set default chat permissions for all members.
|
||||
The bot must be an administrator in the group or a supergroup for this to work
|
||||
and must have the can_restrict_members admin rights.
|
||||
The bot must be an administrator in the group or a supergroup for this to work
|
||||
and must have the can_restrict_members admin rights.
|
||||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target supergroup
|
||||
(in the format @supergroupusername)
|
||||
:param permissions: New default chat permissions
|
||||
:return:
|
||||
:return: True on success
|
||||
"""
|
||||
return apihelper.set_chat_permissions(self.token, chat_id, permissions)
|
||||
|
||||
@ -1080,10 +1198,10 @@ class TeleBot:
|
||||
"""
|
||||
Use this method to export an invite link to a supergroup or a channel. The bot must be an administrator
|
||||
in the chat for this to work and must have the appropriate admin rights.
|
||||
Returns exported invite link as String on success.
|
||||
|
||||
:param chat_id: Id: Unique identifier for the target chat or username of the target channel
|
||||
(in the format @channelusername)
|
||||
:return:
|
||||
:return: exported invite link as String on success.
|
||||
"""
|
||||
return apihelper.export_chat_invite_link(self.token, chat_id)
|
||||
|
||||
@ -1137,15 +1255,15 @@ class TeleBot:
|
||||
"""
|
||||
return apihelper.set_chat_title(self.token, chat_id, title)
|
||||
|
||||
def set_chat_description(self, chat_id, description):
|
||||
def set_chat_description(self, chat_id, description=None):
|
||||
"""
|
||||
Use this method to change the description of a supergroup or a channel.
|
||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
Returns True on success.
|
||||
|
||||
:param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel
|
||||
(in the format @channelusername)
|
||||
:param description: Str: New chat description, 0-255 characters
|
||||
:return:
|
||||
:return: True on success.
|
||||
"""
|
||||
return apihelper.set_chat_description(self.token, chat_id, description)
|
||||
|
||||
@ -1163,16 +1281,28 @@ class TeleBot:
|
||||
"""
|
||||
return apihelper.pin_chat_message(self.token, chat_id, message_id, disable_notification)
|
||||
|
||||
def unpin_chat_message(self, chat_id):
|
||||
def unpin_chat_message(self, chat_id, message_id=None):
|
||||
"""
|
||||
Use this method to unpin a message in a supergroup chat.
|
||||
Use this method to unpin specific pinned message in a supergroup chat.
|
||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
Returns True on success.
|
||||
:param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel
|
||||
(in the format @channelusername)
|
||||
:param message_id: Int: Identifier of a message to unpin
|
||||
:return:
|
||||
"""
|
||||
return apihelper.unpin_chat_message(self.token, chat_id, message_id)
|
||||
|
||||
def unpin_all_chat_messages(self, chat_id):
|
||||
"""
|
||||
Use this method to unpin a all pinned messages in a supergroup chat.
|
||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
Returns True on success.
|
||||
:param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel
|
||||
(in the format @channelusername)
|
||||
:return:
|
||||
"""
|
||||
return apihelper.unpin_chat_message(self.token, chat_id)
|
||||
return apihelper.unpin_all_chat_messages(self.token, chat_id)
|
||||
|
||||
def edit_message_text(self, text, chat_id=None, message_id=None, inline_message_id=None, parse_mode=None,
|
||||
disable_web_page_preview=None, reply_markup=None):
|
||||
@ -1187,7 +1317,7 @@ class TeleBot:
|
||||
:param reply_markup:
|
||||
:return:
|
||||
"""
|
||||
parse_mode = self.parse_mode if not parse_mode else parse_mode
|
||||
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
|
||||
|
||||
result = apihelper.edit_message_text(self.token, text, chat_id, message_id, inline_message_id, parse_mode,
|
||||
disable_web_page_preview, reply_markup)
|
||||
@ -1234,6 +1364,7 @@ class TeleBot:
|
||||
:param disable_notification:
|
||||
:param reply_to_message_id:
|
||||
:param reply_markup:
|
||||
:param timeout:
|
||||
:return:
|
||||
"""
|
||||
result = apihelper.send_game(
|
||||
@ -1278,7 +1409,7 @@ class TeleBot:
|
||||
def send_invoice(self, chat_id, title, description, invoice_payload, provider_token, currency, prices,
|
||||
start_parameter, photo_url=None, photo_size=None, photo_width=None, photo_height=None,
|
||||
need_name=None, need_phone_number=None, need_email=None, need_shipping_address=None,
|
||||
send_phone_number_to_provider = None, send_email_to_provider = None, is_flexible=None,
|
||||
send_phone_number_to_provider=None, send_email_to_provider=None, is_flexible=None,
|
||||
disable_notification=None, reply_to_message_id=None, reply_markup=None, provider_data=None, timeout=None):
|
||||
"""
|
||||
Sends invoice
|
||||
@ -1305,6 +1436,7 @@ class TeleBot:
|
||||
:param reply_to_message_id: If the message is a reply, ID of the original message
|
||||
:param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button
|
||||
:param provider_data: A JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider.
|
||||
:param timeout:
|
||||
:return:
|
||||
"""
|
||||
result = apihelper.send_invoice(
|
||||
@ -1337,11 +1469,12 @@ class TeleBot:
|
||||
:param disable_notifications:
|
||||
:param reply_to_message_id:
|
||||
:param reply_markup:
|
||||
:param timeout:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if isinstance(question, types.Poll):
|
||||
raise Exception("The send_poll signature was changed, please see send_poll function details.")
|
||||
raise RuntimeError("The send_poll signature was changed, please see send_poll function details.")
|
||||
|
||||
return types.Message.de_json(
|
||||
apihelper.send_poll(
|
||||
@ -1351,14 +1484,15 @@ class TeleBot:
|
||||
explanation, explanation_parse_mode, open_period, close_date, is_closed,
|
||||
disable_notifications, reply_to_message_id, reply_markup, timeout))
|
||||
|
||||
def stop_poll(self, chat_id, message_id):
|
||||
def stop_poll(self, chat_id, message_id, reply_markup=None):
|
||||
"""
|
||||
Stops poll
|
||||
:param chat_id:
|
||||
:param message_id:
|
||||
:param reply_markup:
|
||||
:return:
|
||||
"""
|
||||
return types.Poll.de_json(apihelper.stop_poll(self.token, chat_id, message_id))
|
||||
return types.Poll.de_json(apihelper.stop_poll(self.token, chat_id, message_id, reply_markup))
|
||||
|
||||
def answer_shipping_query(self, shipping_query_id, ok, shipping_options=None, error_message=None):
|
||||
"""
|
||||
@ -1393,8 +1527,8 @@ class TeleBot:
|
||||
:param reply_markup:
|
||||
:return:
|
||||
"""
|
||||
parse_mode = self.parse_mode if not parse_mode else parse_mode
|
||||
|
||||
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
|
||||
|
||||
result = apihelper.edit_message_caption(self.token, caption, chat_id, message_id, inline_message_id,
|
||||
parse_mode, reply_markup)
|
||||
if type(result) == bool:
|
||||
@ -1542,8 +1676,9 @@ class TeleBot:
|
||||
for message in new_messages:
|
||||
if hasattr(message, "reply_to_message") and message.reply_to_message is not None:
|
||||
handlers = self.reply_backend.get_handlers(message.reply_to_message.message_id)
|
||||
for handler in handlers:
|
||||
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
|
||||
if handlers:
|
||||
for handler in handlers:
|
||||
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
|
||||
|
||||
def register_next_step_handler(self, message, callback, *args, **kwargs):
|
||||
"""
|
||||
@ -1615,11 +1750,12 @@ class TeleBot:
|
||||
for i, message in enumerate(new_messages):
|
||||
need_pop = False
|
||||
handlers = self.next_step_backend.get_handlers(message.chat.id)
|
||||
for handler in handlers:
|
||||
need_pop = True
|
||||
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
|
||||
if handlers:
|
||||
for handler in handlers:
|
||||
need_pop = True
|
||||
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
|
||||
if need_pop:
|
||||
new_messages.pop(i) # removing message that detects with next_step_handler
|
||||
new_messages.pop(i) # removing message that was detected with next_step_handler
|
||||
|
||||
@staticmethod
|
||||
def _build_handler_dict(handler, **filters):
|
||||
@ -1662,7 +1798,6 @@ class TeleBot:
|
||||
|
||||
def decorator(handler):
|
||||
self.add_middleware_handler(handler, update_types)
|
||||
|
||||
return handler
|
||||
|
||||
return decorator
|
||||
@ -1674,6 +1809,9 @@ class TeleBot:
|
||||
:param update_types:
|
||||
:return:
|
||||
"""
|
||||
if not apihelper.ENABLE_MIDDLEWARE:
|
||||
raise RuntimeError("Middleware is not enabled. Use apihelper.ENABLE_MIDDLEWARE.")
|
||||
|
||||
if update_types:
|
||||
for update_type in update_types:
|
||||
self.typed_middleware_handlers[update_type].append(handler)
|
||||
@ -1721,9 +1859,7 @@ class TeleBot:
|
||||
func=func,
|
||||
content_types=content_types,
|
||||
**kwargs)
|
||||
|
||||
self.add_message_handler(handler_dict)
|
||||
|
||||
return handler
|
||||
|
||||
return decorator
|
||||
@ -1845,6 +1981,7 @@ class TeleBot:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(handler):
|
||||
handler_dict = self._build_handler_dict(handler, func=func, **kwargs)
|
||||
self.add_inline_handler(handler_dict)
|
||||
@ -1867,6 +2004,7 @@ class TeleBot:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(handler):
|
||||
handler_dict = self._build_handler_dict(handler, func=func, **kwargs)
|
||||
self.add_chosen_inline_handler(handler_dict)
|
||||
@ -1889,6 +2027,7 @@ class TeleBot:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(handler):
|
||||
handler_dict = self._build_handler_dict(handler, func=func, **kwargs)
|
||||
self.add_callback_query_handler(handler_dict)
|
||||
@ -1911,6 +2050,7 @@ class TeleBot:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(handler):
|
||||
handler_dict = self._build_handler_dict(handler, func=func, **kwargs)
|
||||
self.add_shipping_query_handler(handler_dict)
|
||||
@ -1933,6 +2073,7 @@ class TeleBot:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(handler):
|
||||
handler_dict = self._build_handler_dict(handler, func=func, **kwargs)
|
||||
self.add_pre_checkout_query_handler(handler_dict)
|
||||
@ -1955,6 +2096,7 @@ class TeleBot:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(handler):
|
||||
handler_dict = self._build_handler_dict(handler, func=func, **kwargs)
|
||||
self.add_poll_handler(handler_dict)
|
||||
@ -1977,6 +2119,7 @@ class TeleBot:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(handler):
|
||||
handler_dict = self._build_handler_dict(handler, func=func, **kwargs)
|
||||
self.add_poll_answer_handler(handler_dict)
|
||||
@ -1999,7 +2142,7 @@ class TeleBot:
|
||||
:param message:
|
||||
:return:
|
||||
"""
|
||||
for message_filter, filter_value in six.iteritems(message_handler['filters']):
|
||||
for message_filter, filter_value in message_handler['filters'].items():
|
||||
if filter_value is None:
|
||||
continue
|
||||
|
||||
@ -2033,6 +2176,8 @@ class TeleBot:
|
||||
:param new_messages:
|
||||
:return:
|
||||
"""
|
||||
if len(handlers) == 0:
|
||||
return
|
||||
for message in new_messages:
|
||||
for message_handler in handlers:
|
||||
if self._test_message_handler(message_handler, message):
|
||||
@ -2189,8 +2334,8 @@ class AsyncTeleBot(TeleBot):
|
||||
return TeleBot.kick_chat_member(self, *args, **kwargs)
|
||||
|
||||
@util.async_dec()
|
||||
def unban_chat_member(self, *args):
|
||||
return TeleBot.unban_chat_member(self, *args)
|
||||
def unban_chat_member(self, *args, **kwargs):
|
||||
return TeleBot.unban_chat_member(self, *args, **kwargs)
|
||||
|
||||
@util.async_dec()
|
||||
def restrict_chat_member(self, *args, **kwargs):
|
||||
@ -2228,6 +2373,10 @@ class AsyncTeleBot(TeleBot):
|
||||
def unpin_chat_message(self, *args):
|
||||
return TeleBot.unpin_chat_message(self, *args)
|
||||
|
||||
@util.async_dec()
|
||||
def unpin_all_chat_messages(self, *args):
|
||||
return TeleBot.unpin_all_chat_messages(self, *args)
|
||||
|
||||
@util.async_dec()
|
||||
def edit_message_text(self, *args, **kwargs):
|
||||
return TeleBot.edit_message_text(self, *args, **kwargs)
|
||||
|
@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
@ -28,6 +29,7 @@ FILE_URL = None
|
||||
|
||||
CONNECT_TIMEOUT = 3.5
|
||||
READ_TIMEOUT = 9999
|
||||
SESSION_TIME_TO_LIVE = None # In seconds. None - live forever, 0 - one-time
|
||||
|
||||
RETRY_ON_ERROR = False
|
||||
RETRY_TIMEOUT = 2
|
||||
@ -39,7 +41,21 @@ ENABLE_MIDDLEWARE = False
|
||||
|
||||
|
||||
def _get_req_session(reset=False):
|
||||
return util.per_thread('req_session', lambda: session if session else requests.session(), reset)
|
||||
if SESSION_TIME_TO_LIVE:
|
||||
# If session TTL is set - check time passed
|
||||
creation_date = util.per_thread('req_session_time', lambda: datetime.now(), reset)
|
||||
if (datetime.now() - creation_date).total_seconds() > SESSION_TIME_TO_LIVE:
|
||||
# Force session reset
|
||||
reset = True
|
||||
# Save reset time
|
||||
util.per_thread('req_session_time', lambda: datetime.now(), True)
|
||||
|
||||
if SESSION_TIME_TO_LIVE == 0:
|
||||
# Session is one-time use
|
||||
return requests.sessions.Session()
|
||||
else:
|
||||
# Session lives some time or forever once created. Default
|
||||
return util.per_thread('req_session', lambda: session if session else requests.sessions.Session(), reset)
|
||||
|
||||
|
||||
def _make_request(token, method_name, method='get', params=None, files=None):
|
||||
@ -52,12 +68,12 @@ def _make_request(token, method_name, method='get', params=None, files=None):
|
||||
:param files: Optional files.
|
||||
:return: The result parsed to a JSON dictionary.
|
||||
"""
|
||||
if API_URL is None:
|
||||
request_url = "https://api.telegram.org/bot{0}/{1}".format(token, method_name)
|
||||
else:
|
||||
if API_URL:
|
||||
request_url = API_URL.format(token, method_name)
|
||||
|
||||
logger.debug("Request: method={0} url={1} params={2} files={3}".format(method, request_url, params, files))
|
||||
else:
|
||||
request_url = "https://api.telegram.org/bot{0}/{1}".format(token, method_name)
|
||||
|
||||
logger.debug("Request: method={0} url={1} params={2} files={3}".format(method, request_url, params, files).replace(token, token.split(':')[0] + ":{TOKEN}"))
|
||||
read_timeout = READ_TIMEOUT
|
||||
connect_timeout = CONNECT_TIMEOUT
|
||||
if files and format_header_param:
|
||||
@ -67,7 +83,15 @@ def _make_request(token, method_name, method='get', params=None, files=None):
|
||||
read_timeout = params.pop('timeout') + 10
|
||||
if 'connect-timeout' in params:
|
||||
connect_timeout = params.pop('connect-timeout') + 10
|
||||
|
||||
if 'long_polling_timeout' in params:
|
||||
# For getUpdates
|
||||
# The only function with timeout on the BOT API side
|
||||
params['timeout'] = params.pop('long_polling_timeout')
|
||||
# Long polling hangs for given time. Read timeout should be greater that long_polling_timeout
|
||||
read_timeout = max(params['timeout'] + 10, read_timeout)
|
||||
|
||||
|
||||
result = None
|
||||
if RETRY_ON_ERROR:
|
||||
got_result = False
|
||||
current_try = 0
|
||||
@ -201,7 +225,8 @@ def send_message(
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def set_webhook(token, url=None, certificate=None, max_connections=None, allowed_updates=None):
|
||||
def set_webhook(token, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None,
|
||||
drop_pending_updates = None, timeout=None):
|
||||
method_url = r'setWebhook'
|
||||
payload = {
|
||||
'url': url if url else "",
|
||||
@ -211,23 +236,36 @@ def set_webhook(token, url=None, certificate=None, max_connections=None, allowed
|
||||
files = {'certificate': certificate}
|
||||
if max_connections:
|
||||
payload['max_connections'] = max_connections
|
||||
if allowed_updates:
|
||||
if allowed_updates is not None: # Empty lists should pass
|
||||
payload['allowed_updates'] = json.dumps(allowed_updates)
|
||||
if ip_address is not None: # Empty string should pass
|
||||
payload['ip_address'] = ip_address
|
||||
if drop_pending_updates is not None: # Any bool value should pass
|
||||
payload['drop_pending_updates'] = drop_pending_updates
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload, files=files)
|
||||
|
||||
|
||||
def delete_webhook(token):
|
||||
def delete_webhook(token, drop_pending_updates=None, timeout=None):
|
||||
method_url = r'deleteWebhook'
|
||||
return _make_request(token, method_url)
|
||||
|
||||
|
||||
def get_webhook_info(token):
|
||||
method_url = r'getWebhookInfo'
|
||||
payload = {}
|
||||
if drop_pending_updates is not None: # Any bool value should pass
|
||||
payload['drop_pending_updates'] = drop_pending_updates
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def get_updates(token, offset=None, limit=None, timeout=None, allowed_updates=None):
|
||||
def get_webhook_info(token, timeout=None):
|
||||
method_url = r'getWebhookInfo'
|
||||
payload = {}
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def get_updates(token, offset=None, limit=None, timeout=None, allowed_updates=None, long_polling_timeout = None):
|
||||
method_url = r'getUpdates'
|
||||
payload = {}
|
||||
if offset:
|
||||
@ -235,8 +273,10 @@ def get_updates(token, offset=None, limit=None, timeout=None, allowed_updates=No
|
||||
if limit:
|
||||
payload['limit'] = limit
|
||||
if timeout:
|
||||
payload['timeout'] = timeout
|
||||
if allowed_updates:
|
||||
payload['connect-timeout'] = timeout
|
||||
if long_polling_timeout:
|
||||
payload['long_polling_timeout'] = long_polling_timeout
|
||||
if allowed_updates is not None: # Empty lists should pass
|
||||
payload['allowed_updates'] = json.dumps(allowed_updates)
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
@ -427,12 +467,14 @@ def stop_message_live_location(
|
||||
|
||||
def send_venue(
|
||||
token, chat_id, latitude, longitude, title, address,
|
||||
foursquare_id=None, disable_notification=None,
|
||||
foursquare_id=None, foursquare_type=None, disable_notification=None,
|
||||
reply_to_message_id=None, reply_markup=None, timeout=None):
|
||||
method_url = r'sendVenue'
|
||||
payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude, 'title': title, 'address': address}
|
||||
if foursquare_id:
|
||||
payload['foursquare_id'] = foursquare_id
|
||||
if foursquare_type:
|
||||
payload['foursquare_type'] = foursquare_type
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if reply_to_message_id:
|
||||
@ -445,13 +487,14 @@ def send_venue(
|
||||
|
||||
|
||||
def send_contact(
|
||||
token, chat_id, phone_number, first_name,
|
||||
last_name=None, disable_notification=None,
|
||||
reply_to_message_id=None, reply_markup=None, timeout=None):
|
||||
token, chat_id, phone_number, first_name, last_name=None, vcard=None,
|
||||
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None):
|
||||
method_url = r'sendContact'
|
||||
payload = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name}
|
||||
if last_name:
|
||||
payload['last_name'] = last_name
|
||||
if vcard:
|
||||
payload['vcard'] = vcard
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if reply_to_message_id:
|
||||
@ -685,14 +728,18 @@ def get_method_by_type(data_type):
|
||||
def kick_chat_member(token, chat_id, user_id, until_date=None):
|
||||
method_url = 'kickChatMember'
|
||||
payload = {'chat_id': chat_id, 'user_id': user_id}
|
||||
if until_date:
|
||||
if isinstance(until_date, datetime):
|
||||
payload['until_date'] = until_date.timestamp()
|
||||
else:
|
||||
payload['until_date'] = until_date
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def unban_chat_member(token, chat_id, user_id):
|
||||
def unban_chat_member(token, chat_id, user_id, only_if_banned):
|
||||
method_url = 'unbanChatMember'
|
||||
payload = {'chat_id': chat_id, 'user_id': user_id}
|
||||
if only_if_banned is not None: # None / True / False
|
||||
payload['only_if_banned'] = only_if_banned
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -703,25 +750,30 @@ def restrict_chat_member(
|
||||
can_add_web_page_previews=None, can_change_info=None,
|
||||
can_invite_users=None, can_pin_messages=None):
|
||||
method_url = 'restrictChatMember'
|
||||
payload = {'chat_id': chat_id, 'user_id': user_id}
|
||||
if until_date is not None:
|
||||
payload['until_date'] = until_date
|
||||
permissions = {}
|
||||
if can_send_messages is not None:
|
||||
payload['can_send_messages'] = can_send_messages
|
||||
permissions['can_send_messages'] = can_send_messages
|
||||
if can_send_media_messages is not None:
|
||||
payload['can_send_media_messages'] = can_send_media_messages
|
||||
permissions['can_send_media_messages'] = can_send_media_messages
|
||||
if can_send_polls is not None:
|
||||
payload['can_send_polls'] = can_send_polls
|
||||
permissions['can_send_polls'] = can_send_polls
|
||||
if can_send_other_messages is not None:
|
||||
payload['can_send_other_messages'] = can_send_other_messages
|
||||
permissions['can_send_other_messages'] = can_send_other_messages
|
||||
if can_add_web_page_previews is not None:
|
||||
payload['can_add_web_page_previews'] = can_add_web_page_previews
|
||||
permissions['can_add_web_page_previews'] = can_add_web_page_previews
|
||||
if can_change_info is not None:
|
||||
payload['can_change_info'] = can_change_info
|
||||
permissions['can_change_info'] = can_change_info
|
||||
if can_invite_users is not None:
|
||||
payload['can_invite_users'] = can_invite_users
|
||||
permissions['can_invite_users'] = can_invite_users
|
||||
if can_pin_messages is not None:
|
||||
payload['can_pin_messages'] = can_pin_messages
|
||||
permissions['can_pin_messages'] = can_pin_messages
|
||||
permissions_json = json.dumps(permissions)
|
||||
payload = {'chat_id': chat_id, 'user_id': user_id, 'permissions': permissions_json}
|
||||
if until_date is not None:
|
||||
if isinstance(until_date, datetime):
|
||||
payload['until_date'] = until_date.timestamp()
|
||||
else:
|
||||
payload['until_date'] = until_date
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -806,7 +858,9 @@ def set_my_commands(token, commands):
|
||||
|
||||
def set_chat_description(token, chat_id, description):
|
||||
method_url = 'setChatDescription'
|
||||
payload = {'chat_id': chat_id, 'description': description}
|
||||
payload = {'chat_id': chat_id}
|
||||
if description is not None: # Allow empty strings
|
||||
payload['description'] = description
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -818,9 +872,17 @@ def pin_chat_message(token, chat_id, message_id, disable_notification=None):
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def unpin_chat_message(token, chat_id):
|
||||
def unpin_chat_message(token, chat_id, message_id):
|
||||
method_url = 'unpinChatMessage'
|
||||
payload = {'chat_id': chat_id}
|
||||
if message_id:
|
||||
payload['message_id'] = message_id
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def unpin_all_chat_messages(token, chat_id):
|
||||
method_url = 'unpinAllChatMessages'
|
||||
payload = {'chat_id': chat_id}
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -1005,6 +1067,7 @@ def send_invoice(
|
||||
:param reply_to_message_id: If the message is a reply, ID of the original message
|
||||
:param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button
|
||||
:param provider_data: A JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider.
|
||||
:param timeout:
|
||||
:return:
|
||||
"""
|
||||
method_url = r'sendInvoice'
|
||||
@ -1207,7 +1270,10 @@ def send_poll(
|
||||
if open_period is not None:
|
||||
payload['open_period'] = open_period
|
||||
if close_date is not None:
|
||||
payload['close_date'] = close_date
|
||||
if isinstance(close_date, datetime):
|
||||
payload['close_date'] = close_date.timestamp()
|
||||
else:
|
||||
payload['close_date'] = close_date
|
||||
if is_closed is not None:
|
||||
payload['is_closed'] = is_closed
|
||||
|
||||
@ -1318,7 +1384,7 @@ class ApiTelegramException(ApiException):
|
||||
"""
|
||||
def __init__(self, function_name, result, result_json):
|
||||
super(ApiTelegramException, self).__init__(
|
||||
"Error code: {0} Description: {1}" \
|
||||
"Error code: {0}. Description: {1}" \
|
||||
.format(result_json['error_code'], result_json['description']),
|
||||
function_name,
|
||||
result)
|
||||
|
@ -32,10 +32,13 @@ class MemoryHandlerBackend(HandlerBackend):
|
||||
self.handlers[handler_group_id] = [handler]
|
||||
|
||||
def clear_handlers(self, handler_group_id):
|
||||
self.handlers.pop(handler_group_id, [])
|
||||
self.handlers.pop(handler_group_id, None)
|
||||
|
||||
def get_handlers(self, handler_group_id):
|
||||
return self.handlers.pop(handler_group_id, [])
|
||||
return self.handlers.pop(handler_group_id, None)
|
||||
|
||||
def load_handlers(self, filename, del_file_after_loading):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class FileHandlerBackend(HandlerBackend):
|
||||
@ -50,19 +53,15 @@ class FileHandlerBackend(HandlerBackend):
|
||||
self.handlers[handler_group_id].append(handler)
|
||||
else:
|
||||
self.handlers[handler_group_id] = [handler]
|
||||
|
||||
self.start_save_timer()
|
||||
|
||||
def clear_handlers(self, handler_group_id):
|
||||
self.handlers.pop(handler_group_id, [])
|
||||
|
||||
self.handlers.pop(handler_group_id, None)
|
||||
self.start_save_timer()
|
||||
|
||||
def get_handlers(self, handler_group_id):
|
||||
handlers = self.handlers.pop(handler_group_id, [])
|
||||
|
||||
handlers = self.handlers.pop(handler_group_id, None)
|
||||
self.start_save_timer()
|
||||
|
||||
return handlers
|
||||
|
||||
def start_save_timer(self):
|
||||
@ -115,11 +114,11 @@ class FileHandlerBackend(HandlerBackend):
|
||||
|
||||
|
||||
class RedisHandlerBackend(HandlerBackend):
|
||||
def __init__(self, handlers=None, host='localhost', port=6379, db=0, prefix='telebot'):
|
||||
def __init__(self, handlers=None, host='localhost', port=6379, db=0, prefix='telebot', password=None):
|
||||
super(RedisHandlerBackend, self).__init__(handlers)
|
||||
from redis import Redis
|
||||
self.prefix = prefix
|
||||
self.redis = Redis(host, port, db)
|
||||
self.redis = Redis(host, port, db, password)
|
||||
|
||||
def _key(self, handle_group_id):
|
||||
return ':'.join((self.prefix, str(handle_group_id)))
|
||||
@ -136,10 +135,9 @@ class RedisHandlerBackend(HandlerBackend):
|
||||
self.redis.delete(self._key(handler_group_id))
|
||||
|
||||
def get_handlers(self, handler_group_id):
|
||||
handlers = []
|
||||
handlers = None
|
||||
value = self.redis.get(self._key(handler_group_id))
|
||||
if value:
|
||||
handlers = pickle.loads(value)
|
||||
self.clear_handlers(handler_group_id)
|
||||
|
||||
return handlers
|
||||
|
234
telebot/types.py
234
telebot/types.py
@ -7,8 +7,6 @@ try:
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
import six
|
||||
|
||||
from telebot import util
|
||||
|
||||
DISABLE_KEYLEN_ERROR = False
|
||||
@ -81,13 +79,13 @@ class JsonDeserializable(object):
|
||||
|
||||
def __str__(self):
|
||||
d = {}
|
||||
for x, y in six.iteritems(self.__dict__):
|
||||
for x, y in self.__dict__.items():
|
||||
if hasattr(y, '__dict__'):
|
||||
d[x] = y.__dict__
|
||||
else:
|
||||
d[x] = y
|
||||
|
||||
return six.text_type(d)
|
||||
return str(d)
|
||||
|
||||
|
||||
class Update(JsonDeserializable):
|
||||
@ -134,18 +132,20 @@ class WebhookInfo(JsonDeserializable):
|
||||
url = obj['url']
|
||||
has_custom_certificate = obj['has_custom_certificate']
|
||||
pending_update_count = obj['pending_update_count']
|
||||
ip_address = obj.get('ip_address')
|
||||
last_error_date = obj.get('last_error_date')
|
||||
last_error_message = obj.get('last_error_message')
|
||||
max_connections = obj.get('max_connections')
|
||||
allowed_updates = obj.get('allowed_updates')
|
||||
return cls(url, has_custom_certificate, pending_update_count, last_error_date, last_error_message,
|
||||
max_connections, allowed_updates)
|
||||
return cls(url, has_custom_certificate, pending_update_count, ip_address, last_error_date,
|
||||
last_error_message, max_connections, allowed_updates)
|
||||
|
||||
def __init__(self, url, has_custom_certificate, pending_update_count, last_error_date, last_error_message,
|
||||
max_connections, allowed_updates):
|
||||
def __init__(self, url, has_custom_certificate, pending_update_count, ip_address, last_error_date,
|
||||
last_error_message, max_connections, allowed_updates):
|
||||
self.url = url
|
||||
self.has_custom_certificate = has_custom_certificate
|
||||
self.pending_update_count = pending_update_count
|
||||
self.ip_address = ip_address
|
||||
self.last_error_date = last_error_date
|
||||
self.last_error_message = last_error_message
|
||||
self.max_connections = max_connections
|
||||
@ -220,8 +220,8 @@ class Chat(JsonDeserializable):
|
||||
username = obj.get('username')
|
||||
first_name = obj.get('first_name')
|
||||
last_name = obj.get('last_name')
|
||||
all_members_are_administrators = obj.get('all_members_are_administrators')
|
||||
photo = ChatPhoto.de_json(obj.get('photo'))
|
||||
bio = obj.get('bio')
|
||||
description = obj.get('description')
|
||||
invite_link = obj.get('invite_link')
|
||||
pinned_message = Message.de_json(obj.get('pinned_message'))
|
||||
@ -229,25 +229,27 @@ class Chat(JsonDeserializable):
|
||||
slow_mode_delay = obj.get('slow_mode_delay')
|
||||
sticker_set_name = obj.get('sticker_set_name')
|
||||
can_set_sticker_set = obj.get('can_set_sticker_set')
|
||||
linked_chat_id = obj.get('linked_chat_id')
|
||||
location = None # Not implemented
|
||||
return cls(
|
||||
id, type, title, username, first_name, last_name,
|
||||
all_members_are_administrators, photo, description, invite_link,
|
||||
photo, bio, description, invite_link,
|
||||
pinned_message, permissions, slow_mode_delay, sticker_set_name,
|
||||
can_set_sticker_set)
|
||||
can_set_sticker_set, linked_chat_id, location)
|
||||
|
||||
def __init__(self, id, type, title=None, username=None, first_name=None,
|
||||
last_name=None, all_members_are_administrators=None,
|
||||
photo=None, description=None, invite_link=None,
|
||||
last_name=None, photo=None, bio=None, description=None, invite_link=None,
|
||||
pinned_message=None, permissions=None, slow_mode_delay=None,
|
||||
sticker_set_name=None, can_set_sticker_set=None):
|
||||
sticker_set_name=None, can_set_sticker_set=None,
|
||||
linked_chat_id=None, location=None):
|
||||
self.id = id
|
||||
self.type = type
|
||||
self.title = title
|
||||
self.username = username
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.all_members_are_administrators = all_members_are_administrators
|
||||
self.photo = photo
|
||||
self.bio = bio
|
||||
self.description = description
|
||||
self.invite_link = invite_link
|
||||
self.pinned_message = pinned_message
|
||||
@ -255,6 +257,8 @@ class Chat(JsonDeserializable):
|
||||
self.slow_mode_delay = slow_mode_delay
|
||||
self.sticker_set_name = sticker_set_name
|
||||
self.can_set_sticker_set = can_set_sticker_set
|
||||
self.linked_chat_id = linked_chat_id
|
||||
self.location = location
|
||||
|
||||
|
||||
class Message(JsonDeserializable):
|
||||
@ -276,6 +280,8 @@ class Message(JsonDeserializable):
|
||||
opts['forward_from_message_id'] = obj.get('forward_from_message_id')
|
||||
if 'forward_signature' in obj:
|
||||
opts['forward_signature'] = obj.get('forward_signature')
|
||||
if 'forward_sender_name' in obj:
|
||||
opts['forward_sender_name'] = obj.get('forward_sender_name')
|
||||
if 'forward_date' in obj:
|
||||
opts['forward_date'] = obj.get('forward_date')
|
||||
if 'reply_to_message' in obj:
|
||||
@ -296,12 +302,13 @@ class Message(JsonDeserializable):
|
||||
if 'audio' in obj:
|
||||
opts['audio'] = Audio.de_json(obj['audio'])
|
||||
content_type = 'audio'
|
||||
if 'animation' in obj:
|
||||
opts['animation'] = Animation.de_json(obj['animation'])
|
||||
content_type = 'animation'
|
||||
if 'document' in obj:
|
||||
opts['document'] = Document.de_json(obj['document'])
|
||||
content_type = 'document'
|
||||
if 'animation' in obj:
|
||||
# Document content type accompanies "animation", so "animation" should be checked below "document" to override it
|
||||
opts['animation'] = Animation.de_json(obj['animation'])
|
||||
content_type = 'animation'
|
||||
if 'game' in obj:
|
||||
opts['game'] = Game.de_json(obj['game'])
|
||||
content_type = 'game'
|
||||
@ -385,6 +392,8 @@ class Message(JsonDeserializable):
|
||||
if 'passport_data' in obj:
|
||||
opts['passport_data'] = obj['passport_data']
|
||||
content_type = 'passport_data'
|
||||
if 'reply_markup' in obj:
|
||||
opts['reply_markup'] = InlineKeyboardMarkup.de_json(obj['reply_markup'])
|
||||
return cls(message_id, from_user, date, chat, content_type, opts, json_string)
|
||||
|
||||
@classmethod
|
||||
@ -410,6 +419,7 @@ class Message(JsonDeserializable):
|
||||
|
||||
def __init__(self, message_id, from_user, date, chat, content_type, options, json_string):
|
||||
self.content_type = content_type
|
||||
self.id = message_id # Lets fix the telegram usability ####up with ID in Message :)
|
||||
self.message_id = message_id
|
||||
self.from_user = from_user
|
||||
self.date = date
|
||||
@ -418,6 +428,7 @@ class Message(JsonDeserializable):
|
||||
self.forward_from_chat = None
|
||||
self.forward_from_message_id = None
|
||||
self.forward_signature = None
|
||||
self.forward_sender_name = None
|
||||
self.forward_date = None
|
||||
self.reply_to_message = None
|
||||
self.edit_date = None
|
||||
@ -454,6 +465,7 @@ class Message(JsonDeserializable):
|
||||
self.invoice = None
|
||||
self.successful_payment = None
|
||||
self.connected_website = None
|
||||
self.reply_markup = None
|
||||
for key in options:
|
||||
setattr(self, key, options[key])
|
||||
self.json = json_string
|
||||
@ -499,7 +511,7 @@ class Message(JsonDeserializable):
|
||||
def func(upd_text, subst_type=None, url=None, user=None):
|
||||
upd_text = upd_text.decode("utf-16-le")
|
||||
if subst_type == "text_mention":
|
||||
subst_type = "url"
|
||||
subst_type = "text_link"
|
||||
url = "tg://user?id={0}".format(user.id)
|
||||
elif subst_type == "mention":
|
||||
url = "https://t.me/{0}".format(upd_text[1:])
|
||||
@ -546,14 +558,16 @@ class MessageEntity(JsonDeserializable):
|
||||
length = obj['length']
|
||||
url = obj.get('url')
|
||||
user = User.de_json(obj.get('user'))
|
||||
return cls(type, offset, length, url, user)
|
||||
language = obj.get('language')
|
||||
return cls(type, offset, length, url, user, language)
|
||||
|
||||
def __init__(self, type, offset, length, url=None, user=None):
|
||||
def __init__(self, type, offset, length, url=None, user=None, language=None):
|
||||
self.type = type
|
||||
self.offset = offset
|
||||
self.length = length
|
||||
self.url = url
|
||||
self.user = user
|
||||
self.language = language
|
||||
|
||||
|
||||
class Dice(JsonSerializable, Dictionaryable, JsonDeserializable):
|
||||
@ -583,13 +597,15 @@ class PhotoSize(JsonDeserializable):
|
||||
if (json_string is None): return None
|
||||
obj = cls.check_json(json_string)
|
||||
file_id = obj['file_id']
|
||||
file_unique_id = obj['file_unique_id']
|
||||
width = obj['width']
|
||||
height = obj['height']
|
||||
file_size = obj.get('file_size')
|
||||
return cls(file_id, width, height, file_size)
|
||||
return cls(file_id, file_unique_id, width, height, file_size)
|
||||
|
||||
def __init__(self, file_id, width, height, file_size=None):
|
||||
def __init__(self, file_id, file_unique_id, width, height, file_size=None):
|
||||
self.file_size = file_size
|
||||
self.file_unique_id = file_unique_id
|
||||
self.height = height
|
||||
self.width = width
|
||||
self.file_id = file_id
|
||||
@ -601,15 +617,17 @@ class Audio(JsonDeserializable):
|
||||
if (json_string is None): return None
|
||||
obj = cls.check_json(json_string)
|
||||
file_id = obj['file_id']
|
||||
file_unique_id = obj['file_unique_id']
|
||||
duration = obj['duration']
|
||||
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)
|
||||
return cls(file_id, file_unique_id, duration, performer, title, mime_type, file_size)
|
||||
|
||||
def __init__(self, file_id, duration, performer=None, title=None, mime_type=None, file_size=None):
|
||||
def __init__(self, file_id, file_unique_id, duration, performer=None, title=None, mime_type=None, file_size=None):
|
||||
self.file_id = file_id
|
||||
self.file_unique_id = file_unique_id
|
||||
self.duration = duration
|
||||
self.performer = performer
|
||||
self.title = title
|
||||
@ -623,13 +641,15 @@ class Voice(JsonDeserializable):
|
||||
if (json_string is None): return None
|
||||
obj = cls.check_json(json_string)
|
||||
file_id = obj['file_id']
|
||||
file_unique_id = obj['file_unique_id']
|
||||
duration = obj['duration']
|
||||
mime_type = obj.get('mime_type')
|
||||
file_size = obj.get('file_size')
|
||||
return cls(file_id, duration, mime_type, file_size)
|
||||
return cls(file_id, file_unique_id, duration, mime_type, file_size)
|
||||
|
||||
def __init__(self, file_id, duration, mime_type=None, file_size=None):
|
||||
def __init__(self, file_id, file_unique_id, duration, mime_type=None, file_size=None):
|
||||
self.file_id = file_id
|
||||
self.file_unique_id = file_unique_id
|
||||
self.duration = duration
|
||||
self.mime_type = mime_type
|
||||
self.file_size = file_size
|
||||
@ -641,16 +661,18 @@ class Document(JsonDeserializable):
|
||||
if (json_string is None): return None
|
||||
obj = cls.check_json(json_string)
|
||||
file_id = obj['file_id']
|
||||
file_unique_id = obj['file_unique_id']
|
||||
thumb = None
|
||||
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)
|
||||
return cls(file_id, file_unique_id, thumb, file_name, mime_type, file_size)
|
||||
|
||||
def __init__(self, file_id, thumb=None, file_name=None, mime_type=None, file_size=None):
|
||||
def __init__(self, file_id, file_unique_id, thumb=None, file_name=None, mime_type=None, file_size=None):
|
||||
self.file_id = file_id
|
||||
self.file_unique_id = file_unique_id
|
||||
self.thumb = thumb
|
||||
self.file_name = file_name
|
||||
self.mime_type = mime_type
|
||||
@ -664,16 +686,18 @@ class Video(JsonDeserializable):
|
||||
return None
|
||||
obj = cls.check_json(json_string)
|
||||
file_id = obj['file_id']
|
||||
file_unique_id = obj['file_unique_id']
|
||||
width = obj['width']
|
||||
height = obj['height']
|
||||
duration = obj['duration']
|
||||
thumb = PhotoSize.de_json(obj.get('thumb'))
|
||||
mime_type = obj.get('mime_type')
|
||||
file_size = obj.get('file_size')
|
||||
return cls(file_id, width, height, duration, thumb, mime_type, file_size)
|
||||
return cls(file_id, file_unique_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):
|
||||
def __init__(self, file_id, file_unique_id, width, height, duration, thumb=None, mime_type=None, file_size=None):
|
||||
self.file_id = file_id
|
||||
self.file_unique_id = file_unique_id
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.duration = duration
|
||||
@ -689,14 +713,16 @@ class VideoNote(JsonDeserializable):
|
||||
return None
|
||||
obj = cls.check_json(json_string)
|
||||
file_id = obj['file_id']
|
||||
file_unique_id = obj['file_unique_id']
|
||||
length = obj['length']
|
||||
duration = obj['duration']
|
||||
thumb = PhotoSize.de_json(obj.get('thumb'))
|
||||
file_size = obj.get('file_size')
|
||||
return cls(file_id, length, duration, thumb, file_size)
|
||||
return cls(file_id, file_unique_id, length, duration, thumb, file_size)
|
||||
|
||||
def __init__(self, file_id, length, duration, thumb=None, file_size=None):
|
||||
def __init__(self, file_id, file_unique_id, length, duration, thumb=None, file_size=None):
|
||||
self.file_id = file_id
|
||||
self.file_unique_id = file_unique_id
|
||||
self.length = length
|
||||
self.duration = duration
|
||||
self.thumb = thumb
|
||||
@ -713,13 +739,15 @@ class Contact(JsonDeserializable):
|
||||
first_name = obj['first_name']
|
||||
last_name = obj.get('last_name')
|
||||
user_id = obj.get('user_id')
|
||||
return cls(phone_number, first_name, last_name, user_id)
|
||||
vcard = obj.get('vcard')
|
||||
return cls(phone_number, first_name, last_name, user_id, vcard)
|
||||
|
||||
def __init__(self, phone_number, first_name, last_name=None, user_id=None):
|
||||
def __init__(self, phone_number, first_name, last_name=None, user_id=None, vcard=None):
|
||||
self.phone_number = phone_number
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.user_id = user_id
|
||||
self.vcard = vcard
|
||||
|
||||
|
||||
class Location(JsonDeserializable):
|
||||
@ -747,13 +775,15 @@ class Venue(JsonDeserializable):
|
||||
title = obj['title']
|
||||
address = obj['address']
|
||||
foursquare_id = obj.get('foursquare_id')
|
||||
return cls(location, title, address, foursquare_id)
|
||||
foursquare_type = obj.get('foursquare_type')
|
||||
return cls(location, title, address, foursquare_id, foursquare_type)
|
||||
|
||||
def __init__(self, location, title, address, foursquare_id=None):
|
||||
def __init__(self, location, title, address, foursquare_id=None, foursquare_type=None):
|
||||
self.location = location
|
||||
self.title = title
|
||||
self.address = address
|
||||
self.foursquare_id = foursquare_id
|
||||
self.foursquare_type = foursquare_type
|
||||
|
||||
|
||||
class UserProfilePhotos(JsonDeserializable):
|
||||
@ -778,12 +808,14 @@ class File(JsonDeserializable):
|
||||
return None
|
||||
obj = cls.check_json(json_string)
|
||||
file_id = obj['file_id']
|
||||
file_unique_id = obj['file_unique_id']
|
||||
file_size = obj.get('file_size')
|
||||
file_path = obj.get('file_path')
|
||||
return cls(file_id, file_size, file_path)
|
||||
return cls(file_id, file_unique_id, file_size, file_path)
|
||||
|
||||
def __init__(self, file_id, file_size, file_path):
|
||||
def __init__(self, file_id, file_unique_id, file_size, file_path):
|
||||
self.file_id = file_id
|
||||
self.file_unique_id = file_unique_id
|
||||
self.file_size = file_size
|
||||
self.file_path = file_path
|
||||
|
||||
@ -916,10 +948,18 @@ class KeyboardButtonPollType(Dictionaryable):
|
||||
return {'type': self.type}
|
||||
|
||||
|
||||
class InlineKeyboardMarkup(Dictionaryable, JsonSerializable):
|
||||
class InlineKeyboardMarkup(Dictionaryable, JsonSerializable, JsonDeserializable):
|
||||
max_row_keys = 8
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, json_string):
|
||||
if (json_string is None):
|
||||
return None
|
||||
obj = cls.check_json(json_string)
|
||||
keyboard = [[InlineKeyboardButton.de_json(button) for button in row] for row in obj['inline_keyboard']]
|
||||
return cls(keyboard)
|
||||
|
||||
def __init__(self, row_width=3):
|
||||
def __init__(self, keyboard=None ,row_width=3):
|
||||
"""
|
||||
This object represents an inline keyboard that appears
|
||||
right next to the message it belongs to.
|
||||
@ -932,13 +972,13 @@ class InlineKeyboardMarkup(Dictionaryable, JsonSerializable):
|
||||
row_width = self.max_row_keys
|
||||
|
||||
self.row_width = row_width
|
||||
self.keyboard = []
|
||||
self.keyboard = keyboard if keyboard else []
|
||||
|
||||
def add(self, *args, row_width=None):
|
||||
"""
|
||||
This method adds buttons to the keyboard without exceeding row_width.
|
||||
|
||||
E.g. InlineKeyboardMarkup#add("A", "B", "C") yields the json result:
|
||||
E.g. InlineKeyboardMarkup.add("A", "B", "C") yields the json result:
|
||||
{keyboard: [["A"], ["B"], ["C"]]}
|
||||
when row_width is set to 1.
|
||||
When row_width is set to 2, the result:
|
||||
@ -958,7 +998,7 @@ class InlineKeyboardMarkup(Dictionaryable, JsonSerializable):
|
||||
row_width = self.max_row_keys
|
||||
|
||||
for row in util.chunks(args, row_width):
|
||||
button_array = [button.to_dict() for button in row]
|
||||
button_array = [button for button in row]
|
||||
self.keyboard.append(button_array)
|
||||
|
||||
return self
|
||||
@ -988,16 +1028,28 @@ class InlineKeyboardMarkup(Dictionaryable, JsonSerializable):
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
def to_dict(self):
|
||||
json_dict = {'inline_keyboard': self.keyboard}
|
||||
json_dict = dict()
|
||||
json_dict['inline_keyboard'] = [[button.to_dict() for button in row] for row in self.keyboard]
|
||||
return json_dict
|
||||
|
||||
|
||||
class LoginUrl(Dictionaryable, JsonSerializable):
|
||||
class LoginUrl(Dictionaryable, JsonSerializable, JsonDeserializable):
|
||||
def __init__(self, url, forward_text=None, bot_username=None, request_write_access=None):
|
||||
self.url = url
|
||||
self.forward_text = forward_text
|
||||
self.bot_username = bot_username
|
||||
self.request_write_access = request_write_access
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, json_string):
|
||||
if (json_string is None):
|
||||
return None
|
||||
obj = cls.check_json(json_string)
|
||||
url = obj['url']
|
||||
forward_text = obj.get('forward_text')
|
||||
bot_username = obj.get('bot_username')
|
||||
request_write_access = obj.get('request_write_access')
|
||||
return cls(url, forward_text, bot_username, request_write_access)
|
||||
|
||||
def to_json(self):
|
||||
return json.dumps(self.to_dict())
|
||||
@ -1008,12 +1060,12 @@ class LoginUrl(Dictionaryable, JsonSerializable):
|
||||
json_dict['forward_text'] = self.forward_text
|
||||
if self.bot_username:
|
||||
json_dict['bot_username'] = self.bot_username
|
||||
if self.request_write_access:
|
||||
if self.request_write_access is not None:
|
||||
json_dict['request_write_access'] = self.request_write_access
|
||||
return json_dict
|
||||
|
||||
|
||||
class InlineKeyboardButton(Dictionaryable, JsonSerializable):
|
||||
class InlineKeyboardButton(Dictionaryable, JsonSerializable, JsonDeserializable):
|
||||
def __init__(self, text, url=None, callback_data=None, switch_inline_query=None,
|
||||
switch_inline_query_current_chat=None, callback_game=None, pay=None, login_url=None):
|
||||
self.text = text
|
||||
@ -1024,6 +1076,21 @@ class InlineKeyboardButton(Dictionaryable, JsonSerializable):
|
||||
self.callback_game = callback_game
|
||||
self.pay = pay
|
||||
self.login_url = login_url
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, json_string):
|
||||
if (json_string is None):
|
||||
return None
|
||||
obj = cls.check_json(json_string)
|
||||
text = obj['text']
|
||||
url = obj.get('url')
|
||||
callback_data = obj.get('callback_data')
|
||||
switch_inline_query = obj.get('switch_inline_query')
|
||||
switch_inline_query_current_chat = obj.get('switch_inline_query_current_chat')
|
||||
callback_game = obj.get('callback_game')
|
||||
pay = obj.get('pay')
|
||||
login_url = LoginUrl.de_json(obj.get('login_url'))
|
||||
return cls(text, url, callback_data, switch_inline_query, switch_inline_query_current_chat, callback_game, pay, login_url)
|
||||
|
||||
def to_json(self):
|
||||
return json.dumps(self.to_dict())
|
||||
@ -1078,12 +1145,16 @@ class ChatPhoto(JsonDeserializable):
|
||||
return None
|
||||
obj = cls.check_json(json_string)
|
||||
small_file_id = obj['small_file_id']
|
||||
small_file_unique_id = obj['small_file_unique_id']
|
||||
big_file_id = obj['big_file_id']
|
||||
return cls(small_file_id, big_file_id)
|
||||
big_file_unique_id = obj['big_file_unique_id']
|
||||
return cls(small_file_id, small_file_unique_id, big_file_id, big_file_unique_id)
|
||||
|
||||
def __init__(self, small_file_id, big_file_id):
|
||||
def __init__(self, small_file_id, small_file_unique_id, big_file_id, big_file_unique_id):
|
||||
self.small_file_id = small_file_id
|
||||
self.small_file_unique_id = small_file_unique_id
|
||||
self.big_file_id = big_file_id
|
||||
self.big_file_unique_id = big_file_unique_id
|
||||
|
||||
|
||||
class ChatMember(JsonDeserializable):
|
||||
@ -1095,7 +1166,6 @@ class ChatMember(JsonDeserializable):
|
||||
user = User.de_json(obj['user'])
|
||||
status = obj['status']
|
||||
custom_title = obj.get('custom_title')
|
||||
until_date = obj.get('until_date')
|
||||
can_be_edited = obj.get('can_be_edited')
|
||||
can_post_messages = obj.get('can_post_messages')
|
||||
can_edit_messages = obj.get('can_edit_messages')
|
||||
@ -1111,23 +1181,23 @@ class ChatMember(JsonDeserializable):
|
||||
can_send_polls = obj.get('can_send_polls')
|
||||
can_send_other_messages = obj.get('can_send_other_messages')
|
||||
can_add_web_page_previews = obj.get('can_add_web_page_previews')
|
||||
until_date = obj.get('until_date')
|
||||
return cls(
|
||||
user, status, custom_title, until_date, can_be_edited, can_post_messages,
|
||||
user, status, custom_title, can_be_edited, can_post_messages,
|
||||
can_edit_messages, can_delete_messages, can_restrict_members,
|
||||
can_promote_members, can_change_info, can_invite_users, can_pin_messages,
|
||||
is_member, can_send_messages, can_send_media_messages, can_send_polls,
|
||||
can_send_other_messages, can_add_web_page_previews)
|
||||
can_send_other_messages, can_add_web_page_previews, until_date)
|
||||
|
||||
def __init__(self, user, status, custom_title=None, until_date=None, can_be_edited=None,
|
||||
def __init__(self, user, status, custom_title=None, can_be_edited=None,
|
||||
can_post_messages=None, can_edit_messages=None, can_delete_messages=None,
|
||||
can_restrict_members=None, can_promote_members=None, can_change_info=None,
|
||||
can_invite_users=None, can_pin_messages=None, is_member=None,
|
||||
can_send_messages=None, can_send_media_messages=None, can_send_polls=None,
|
||||
can_send_other_messages=None, can_add_web_page_previews=None):
|
||||
can_send_other_messages=None, can_add_web_page_previews=None, until_date=None):
|
||||
self.user = user
|
||||
self.status = status
|
||||
self.custom_title = custom_title
|
||||
self.until_date = until_date
|
||||
self.can_be_edited = can_be_edited
|
||||
self.can_post_messages = can_post_messages
|
||||
self.can_edit_messages = can_edit_messages
|
||||
@ -1143,6 +1213,7 @@ class ChatMember(JsonDeserializable):
|
||||
self.can_send_polls = can_send_polls
|
||||
self.can_send_other_messages = can_send_other_messages
|
||||
self.can_add_web_page_previews = can_add_web_page_previews
|
||||
self.until_date = until_date
|
||||
|
||||
|
||||
class ChatPermissions(JsonDeserializable, JsonSerializable, Dictionaryable):
|
||||
@ -1283,12 +1354,13 @@ class InputLocationMessageContent(Dictionaryable):
|
||||
|
||||
|
||||
class InputVenueMessageContent(Dictionaryable):
|
||||
def __init__(self, latitude, longitude, title, address, foursquare_id=None):
|
||||
def __init__(self, latitude, longitude, title, address, foursquare_id=None, foursquare_type=None):
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.title = title
|
||||
self.address = address
|
||||
self.foursquare_id = foursquare_id
|
||||
self.foursquare_type = foursquare_type
|
||||
|
||||
def to_dict(self):
|
||||
json_dict = {
|
||||
@ -1299,19 +1371,24 @@ class InputVenueMessageContent(Dictionaryable):
|
||||
}
|
||||
if self.foursquare_id:
|
||||
json_dict['foursquare_id'] = self.foursquare_id
|
||||
if self.foursquare_type:
|
||||
json_dict['foursquare_type'] = self.foursquare_type
|
||||
return json_dict
|
||||
|
||||
|
||||
class InputContactMessageContent(Dictionaryable):
|
||||
def __init__(self, phone_number, first_name, last_name=None):
|
||||
def __init__(self, phone_number, first_name, last_name=None, vcard=None):
|
||||
self.phone_number = phone_number
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.vcard = vcard
|
||||
|
||||
def to_dict(self):
|
||||
json_dict = {'phone_numbe': self.phone_number, 'first_name': self.first_name}
|
||||
if self.last_name:
|
||||
json_dict['last_name'] = self.last_name
|
||||
if self.vcard:
|
||||
json_dict['vcard'] = self.vcard
|
||||
return json_dict
|
||||
|
||||
|
||||
@ -1739,8 +1816,8 @@ class InlineQueryResultLocation(JsonSerializable):
|
||||
|
||||
|
||||
class InlineQueryResultVenue(JsonSerializable):
|
||||
def __init__(self, id, title, latitude, longitude, address, foursquare_id=None, reply_markup=None,
|
||||
input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None):
|
||||
def __init__(self, id, title, latitude, longitude, address, foursquare_id=None, foursquare_type=None,
|
||||
reply_markup=None, input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None):
|
||||
self.type = 'venue'
|
||||
self.id = id
|
||||
self.title = title
|
||||
@ -1748,6 +1825,7 @@ class InlineQueryResultVenue(JsonSerializable):
|
||||
self.longitude = longitude
|
||||
self.address = address
|
||||
self.foursquare_id = foursquare_id
|
||||
self.foursquare_type = foursquare_type
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.thumb_url = thumb_url
|
||||
@ -1759,6 +1837,8 @@ class InlineQueryResultVenue(JsonSerializable):
|
||||
'longitude': self.longitude, 'address': self.address}
|
||||
if self.foursquare_id:
|
||||
json_dict['foursquare_id'] = self.foursquare_id
|
||||
if self.foursquare_type:
|
||||
json_dict['foursquare_type'] = self.foursquare_type
|
||||
if self.thumb_url:
|
||||
json_dict['thumb_url'] = self.thumb_url
|
||||
if self.thumb_width:
|
||||
@ -1773,13 +1853,15 @@ class InlineQueryResultVenue(JsonSerializable):
|
||||
|
||||
|
||||
class InlineQueryResultContact(JsonSerializable):
|
||||
def __init__(self, id, phone_number, first_name, last_name=None, reply_markup=None,
|
||||
input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None):
|
||||
def __init__(self, id, phone_number, first_name, last_name=None, vcard=None,
|
||||
reply_markup=None, input_message_content=None,
|
||||
thumb_url=None, thumb_width=None, thumb_height=None):
|
||||
self.type = 'contact'
|
||||
self.id = id
|
||||
self.phone_number = phone_number
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.vcard = vcard
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.thumb_url = thumb_url
|
||||
@ -1790,16 +1872,18 @@ class InlineQueryResultContact(JsonSerializable):
|
||||
json_dict = {'type': self.type, 'id': self.id, 'phone_number': self.phone_number, 'first_name': self.first_name}
|
||||
if self.last_name:
|
||||
json_dict['last_name'] = self.last_name
|
||||
if self.vcard:
|
||||
json_dict['vcard'] = self.vcard
|
||||
if self.reply_markup:
|
||||
json_dict['reply_markup'] = self.reply_markup.to_dict()
|
||||
if self.input_message_content:
|
||||
json_dict['input_message_content'] = self.input_message_content.to_dict()
|
||||
if self.thumb_url:
|
||||
json_dict['thumb_url'] = self.thumb_url
|
||||
if self.thumb_width:
|
||||
json_dict['thumb_width'] = self.thumb_width
|
||||
if self.thumb_height:
|
||||
json_dict['thumb_height'] = self.thumb_height
|
||||
if self.reply_markup:
|
||||
json_dict['reply_markup'] = self.reply_markup.to_dict()
|
||||
if self.input_message_content:
|
||||
json_dict['input_message_content'] = self.input_message_content.to_dict()
|
||||
return json.dumps(json_dict)
|
||||
|
||||
|
||||
@ -1978,9 +2062,10 @@ class Game(JsonDeserializable):
|
||||
description = obj['description']
|
||||
photo = Game.parse_photo(obj['photo'])
|
||||
text = obj.get('text')
|
||||
text_entities = None
|
||||
if 'text_entities' in obj:
|
||||
text_entities = Game.parse_entities(obj['text_entities'])
|
||||
else:
|
||||
text_entities = None
|
||||
animation = Animation.de_json(obj.get('animation'))
|
||||
return cls(title, description, photo, text, text_entities, animation)
|
||||
|
||||
@ -2013,14 +2098,16 @@ class Animation(JsonDeserializable):
|
||||
if (json_string is None): return None
|
||||
obj = cls.check_json(json_string)
|
||||
file_id = obj['file_id']
|
||||
file_unique_id = obj['file_unique_id']
|
||||
thumb = PhotoSize.de_json(obj.get('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)
|
||||
return cls(file_id, file_unique_id, thumb, file_name, mime_type, file_size)
|
||||
|
||||
def __init__(self, file_id, thumb=None, file_name=None, mime_type=None, file_size=None):
|
||||
def __init__(self, file_id, file_unique_id, thumb=None, file_name=None, mime_type=None, file_size=None):
|
||||
self.file_id = file_id
|
||||
self.file_unique_id = file_unique_id
|
||||
self.thumb = thumb
|
||||
self.file_name = file_name
|
||||
self.mime_type = mime_type
|
||||
@ -2216,14 +2303,16 @@ class StickerSet(JsonDeserializable):
|
||||
obj = cls.check_json(json_string)
|
||||
name = obj['name']
|
||||
title = obj['title']
|
||||
is_animated = obj['is_animated']
|
||||
contains_masks = obj['contains_masks']
|
||||
stickers = []
|
||||
for s in obj['stickers']:
|
||||
stickers.append(Sticker.de_json(s))
|
||||
return cls(name, title, contains_masks, stickers)
|
||||
return cls(name, title, is_animated, contains_masks, stickers)
|
||||
|
||||
def __init__(self, name, title, contains_masks, stickers):
|
||||
def __init__(self, name, title, is_animated, contains_masks, stickers):
|
||||
self.stickers = stickers
|
||||
self.is_animated = is_animated
|
||||
self.contains_masks = contains_masks
|
||||
self.title = title
|
||||
self.name = name
|
||||
@ -2448,7 +2537,6 @@ class Poll(JsonDeserializable):
|
||||
explanation_entities = None
|
||||
open_period = obj.get('open_period')
|
||||
close_date = obj.get('close_date')
|
||||
#poll =
|
||||
return cls(
|
||||
question, options,
|
||||
poll_id, total_voter_count, is_closed, is_anonymous, poll_type,
|
||||
@ -2471,7 +2559,7 @@ class Poll(JsonDeserializable):
|
||||
self.allows_multiple_answers = allows_multiple_answers
|
||||
self.correct_option_id = correct_option_id
|
||||
self.explanation = explanation
|
||||
self.explanation_entities = explanation_entities if not(explanation_entities is None) else []
|
||||
self.explanation_entities = explanation_entities # Default state of entities is None. if (explanation_entities is not None) else []
|
||||
self.open_period = open_period
|
||||
self.close_date = close_date
|
||||
|
||||
|
@ -2,25 +2,16 @@
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import warnings
|
||||
import functools
|
||||
|
||||
import six
|
||||
from six import string_types
|
||||
|
||||
# Python3 queue support.
|
||||
|
||||
try:
|
||||
import Queue
|
||||
except ImportError:
|
||||
import queue as Queue
|
||||
import queue as Queue
|
||||
import logging
|
||||
|
||||
try:
|
||||
import PIL
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
pil_imported = True
|
||||
except:
|
||||
@ -30,6 +21,15 @@ logger = logging.getLogger('TeleBot')
|
||||
|
||||
thread_local = threading.local()
|
||||
|
||||
content_type_media = [
|
||||
'text', 'audio', 'document', 'photo', 'sticker', 'video', 'video_note', 'voice', 'contact', 'dice', 'poll',
|
||||
'venue', 'location'
|
||||
]
|
||||
|
||||
content_type_service = [
|
||||
'new_chat_members', 'left_chat_member', 'new_chat_title', 'new_chat_photo', 'delete_chat_photo', 'group_chat_created',
|
||||
'supergroup_chat_created', 'channel_chat_created', 'migrate_to_chat_id', 'migrate_from_chat_id', 'pinned_message'
|
||||
]
|
||||
|
||||
class WorkerThread(threading.Thread):
|
||||
count = 0
|
||||
@ -51,7 +51,7 @@ class WorkerThread(threading.Thread):
|
||||
self.continue_event = threading.Event()
|
||||
|
||||
self.exception_callback = exception_callback
|
||||
self.exc_info = None
|
||||
self.exception_info = None
|
||||
self._running = True
|
||||
self.start()
|
||||
|
||||
@ -72,12 +72,11 @@ class WorkerThread(threading.Thread):
|
||||
except Queue.Empty:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(type(e).__name__ + " occurred, args=" + str(e.args) + "\n" + traceback.format_exc())
|
||||
self.exc_info = sys.exc_info()
|
||||
logger.debug(type(e).__name__ + " occurred, args=" + str(e.args) + "\n" + traceback.format_exc())
|
||||
self.exception_info = e
|
||||
self.exception_event.set()
|
||||
|
||||
if self.exception_callback:
|
||||
self.exception_callback(self, self.exc_info)
|
||||
self.exception_callback(self, self.exception_info)
|
||||
self.continue_event.wait()
|
||||
|
||||
def put(self, task, *args, **kwargs):
|
||||
@ -85,7 +84,7 @@ class WorkerThread(threading.Thread):
|
||||
|
||||
def raise_exceptions(self):
|
||||
if self.exception_event.is_set():
|
||||
six.reraise(self.exc_info[0], self.exc_info[1], self.exc_info[2])
|
||||
raise self.exception_info
|
||||
|
||||
def clear_exceptions(self):
|
||||
self.exception_event.clear()
|
||||
@ -103,19 +102,19 @@ class ThreadPool:
|
||||
self.num_threads = num_threads
|
||||
|
||||
self.exception_event = threading.Event()
|
||||
self.exc_info = None
|
||||
self.exception_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_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])
|
||||
raise self.exception_info
|
||||
|
||||
def clear_exceptions(self):
|
||||
self.exception_event.clear()
|
||||
@ -140,15 +139,15 @@ class AsyncTask:
|
||||
def _run(self):
|
||||
try:
|
||||
self.result = self.target(*self.args, **self.kwargs)
|
||||
except:
|
||||
self.result = sys.exc_info()
|
||||
except Exception as e:
|
||||
self.result = e
|
||||
self.done = True
|
||||
|
||||
def wait(self):
|
||||
if not self.done:
|
||||
self.thread.join()
|
||||
if isinstance(self.result, BaseException):
|
||||
six.reraise(self.result[0], self.result[1], self.result[2])
|
||||
raise self.result
|
||||
else:
|
||||
return self.result
|
||||
|
||||
@ -164,7 +163,7 @@ def async_dec():
|
||||
|
||||
|
||||
def is_string(var):
|
||||
return isinstance(var, string_types)
|
||||
return isinstance(var, str)
|
||||
|
||||
def is_dict(var):
|
||||
return isinstance(var, dict)
|
||||
@ -173,7 +172,7 @@ def is_bytes(var):
|
||||
return isinstance(var, bytes)
|
||||
|
||||
def is_pil_image(var):
|
||||
return pil_imported and isinstance(var, PIL.Image.Image)
|
||||
return pil_imported and isinstance(var, Image.Image)
|
||||
|
||||
def pil_image_to_file(image, extension='JPEG', quality='web_low'):
|
||||
if pil_imported:
|
||||
@ -236,8 +235,10 @@ def or_clear(self):
|
||||
|
||||
|
||||
def orify(e, changed_callback):
|
||||
e._set = e.set
|
||||
e._clear = e.clear
|
||||
if not hasattr(e, "_set"):
|
||||
e._set = e.set
|
||||
if not hasattr(e, "_clear"):
|
||||
e._clear = e.clear
|
||||
e.changed = changed_callback
|
||||
e.set = lambda: or_set(e)
|
||||
e.clear = lambda: or_clear(e)
|
||||
@ -245,7 +246,7 @@ def orify(e, changed_callback):
|
||||
def OrEvent(*events):
|
||||
or_event = threading.Event()
|
||||
def changed():
|
||||
bools = [e.is_set() for e in events]
|
||||
bools = [ev.is_set() for ev in events]
|
||||
if any(bools):
|
||||
or_event.set()
|
||||
else:
|
||||
@ -295,7 +296,6 @@ def chunks(lst, n):
|
||||
def generate_random_token():
|
||||
return ''.join(random.sample(string.ascii_letters, 16))
|
||||
|
||||
|
||||
def deprecated(func):
|
||||
"""This is a decorator which can be used to mark functions
|
||||
as deprecated. It will result in a warning being emitted
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Versions should comply with PEP440.
|
||||
# This line is parsed in setup.py:
|
||||
__version__ = '3.7.3'
|
||||
__version__ = '3.7.6'
|
||||
|
@ -48,6 +48,7 @@ class TestTeleBot:
|
||||
bot = telebot.TeleBot('')
|
||||
msg = self.create_text_message(r'https://web.telegram.org/')
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@bot.message_handler(regexp=r'((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!)?)*)')
|
||||
def command_url(message):
|
||||
msg.text = 'got'
|
||||
@ -60,6 +61,7 @@ class TestTeleBot:
|
||||
bot = telebot.TeleBot('')
|
||||
msg = self.create_text_message(r'lambda_text')
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@bot.message_handler(func=lambda message: r'lambda' in message.text)
|
||||
def command_url(message):
|
||||
msg.text = 'got'
|
||||
@ -72,6 +74,7 @@ class TestTeleBot:
|
||||
bot = telebot.TeleBot('')
|
||||
msg = self.create_text_message(r'text')
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@bot.message_handler(func=lambda message: r'lambda' in message.text)
|
||||
def command_url(message):
|
||||
msg.text = 'got'
|
||||
@ -84,6 +87,7 @@ class TestTeleBot:
|
||||
bot = telebot.TeleBot('')
|
||||
msg = self.create_text_message(r'web.telegram.org/')
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@bot.message_handler(regexp=r'((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!)?)*)')
|
||||
def command_url(message):
|
||||
msg.text = 'got'
|
||||
@ -522,6 +526,7 @@ class TestTeleBot:
|
||||
tb = telebot.TeleBot('')
|
||||
update = self.create_message_update('/help')
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@tb.middleware_handler(update_types=['message'])
|
||||
def middleware(tb_instance, message):
|
||||
message.text = 'got'
|
||||
@ -542,9 +547,10 @@ class TestTeleBot:
|
||||
tb = telebot.TeleBot('')
|
||||
update = self.create_message_update('/help')
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@tb.middleware_handler()
|
||||
def middleware(tb_instance, update):
|
||||
update.message.text = 'got'
|
||||
def middleware(tb_instance, mw_update):
|
||||
mw_update.message.text = 'got'
|
||||
|
||||
@tb.message_handler(func=lambda m: m.text == 'got')
|
||||
def command_handler(message):
|
||||
@ -556,6 +562,6 @@ class TestTeleBot:
|
||||
|
||||
def test_chat_permissions(self):
|
||||
return # CHAT_ID is private chat, no permissions can be set
|
||||
tb = telebot.TeleBot(TOKEN)
|
||||
permissions = types.ChatPermissions(can_send_messages=True, can_send_polls=False)
|
||||
msg = tb.set_chat_permissions(CHAT_ID, permissions)
|
||||
#tb = telebot.TeleBot(TOKEN)
|
||||
#permissions = types.ChatPermissions(can_send_messages=True, can_send_polls=False)
|
||||
#msg = tb.set_chat_permissions(CHAT_ID, permissions)
|
||||
|
@ -17,6 +17,28 @@ def test_json_message():
|
||||
assert msg.text == 'HIHI'
|
||||
|
||||
|
||||
def test_json_message_with_reply_markup():
|
||||
jsonstring = r'{"message_id":48,"from":{"id":153587469,"is_bot":false,"first_name":"Neko","username":"Neko"},"chat":{"id":153587469,"first_name":"Neko","username":"Neko","type":"private"},"date":1598879570,"text":"test","reply_markup":{"inline_keyboard":[[{"text":"Google","url":"http://www.google.com"},{"text":"Yahoo","url":"http://www.yahoo.com"}]]}}'
|
||||
msg = types.Message.de_json(jsonstring)
|
||||
assert msg.content_type == 'text'
|
||||
assert msg.reply_markup.keyboard[0][0].text == 'Google'
|
||||
|
||||
|
||||
def test_json_InlineKeyboardMarkup():
|
||||
jsonstring = r'{"inline_keyboard":[[{"text":"Google","url":"http://www.google.com"},{"text":"Yahoo","url":"http://www.yahoo.com"}]]}'
|
||||
markup = types.InlineKeyboardMarkup.de_json(jsonstring)
|
||||
assert markup.keyboard[0][0].text == 'Google'
|
||||
assert markup.keyboard[0][1].url == 'http://www.yahoo.com'
|
||||
|
||||
|
||||
def test_json_InlineKeyboardButton():
|
||||
jsonstring = r'{"text":"Google","url":"http://www.google.com"}'
|
||||
button = types.InlineKeyboardButton.de_json(jsonstring)
|
||||
assert button.text == 'Google'
|
||||
assert button.url == 'http://www.google.com'
|
||||
|
||||
|
||||
|
||||
def test_json_message_with_dice():
|
||||
jsonstring = r'{"message_id":5560,"from":{"id":879343317,"is_bot":false,"first_name":"George","last_name":"Forse","username":"dr_fxrse","language_code":"ru"},"chat":{"id":879343317,"first_name":"George","last_name":"Forse","username":"dr_fxrse","type":"private"},"date":1586926330,"dice":{"value": 4, "emoji": "\ud83c\udfaf"}}'
|
||||
msg = types.Message.de_json(jsonstring)
|
||||
@ -41,14 +63,14 @@ def test_json_GroupChat():
|
||||
|
||||
|
||||
def test_json_Document():
|
||||
json_string = r'{"file_name":"Text File","thumb":{},"file_id":"BQADBQADMwIAAsYifgZ_CEh0u682xwI","file_size":446}'
|
||||
json_string = r'{"file_name":"Text File","thumb":{},"file_id":"BQADBQADMwIAAsYifgZ_CEh0u682xwI","file_unique_id": "AgADJQEAAqfhOEY","file_size":446}'
|
||||
doc = types.Document.de_json(json_string)
|
||||
assert doc.thumb is None
|
||||
assert doc.file_name == 'Text File'
|
||||
|
||||
|
||||
def test_json_Message_Audio():
|
||||
json_string = r'{"message_id":131,"from":{"id":12775,"first_name":"dd","username":"dd","is_bot":true },"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}}'
|
||||
json_string = r'{"message_id":131,"from":{"id":12775,"first_name":"dd","username":"dd","is_bot":true },"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_unique_id": "AgADawEAAn8VSFY","file_size":20096}}'
|
||||
msg = types.Message.de_json(json_string)
|
||||
assert msg.audio.duration == 1
|
||||
assert msg.content_type == 'audio'
|
||||
@ -73,21 +95,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","is_bot":true },"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}}'
|
||||
json_string = r'{"message_id":97,"from":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd","is_bot":true },"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_unique_id": "AQAD_QIfa3QAAyA4BgAB","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","is_bot":true },"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}]}'
|
||||
json_string = r'{"message_id":96,"from":{"id":109734,"first_name":"Fd","last_name":"Wd","username":"dd","is_bot":true },"chat":{"id":10734,"first_name":"Fd","type":"private","last_name":"dd","username":"dd"},"date":1435478191,"photo":[{"file_id":"AgADBQADIagxG8YifgYv8yLSj76i-dd","file_unique_id": "AQAD_QIfa3QAAyA4BgAB","file_size":615,"width":90,"height":67},{"file_id":"AgADBQADIagxG8YifgYv8yLSj76i-dd","file_unique_id": "AQAD_QIfa3QAAyA4BgAB","file_size":10174,"width":320,"height":240},{"file_id":"dd-A_LsTIABFNx-FUOaEa_3AABAQABAg","file_unique_id": "AQAD_QIfa3QAAyA4BgAB","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","is_bot":true },"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}}'
|
||||
json_string = r'{"message_id":101,"from":{"id":109734,"first_name":"dd","last_name":"dd","username":"dd","is_bot":true },"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_unique_id": "AQADTeisa3QAAz1nAAI","file_size":1597,"width":50,"height":90},"file_id":"BAADBQADNifgb_TOPEKErGoQI","file_unique_id": "AgADbgEAAn8VSFY","file_size":260699}}'
|
||||
msg = types.Message.de_json(json_string)
|
||||
assert msg.video
|
||||
assert msg.video.duration == 3
|
||||
@ -103,21 +125,21 @@ def test_json_Message_Location():
|
||||
|
||||
|
||||
def test_json_UserProfilePhotos():
|
||||
json_string = r'{"total_count":1,"photos":[[{"file_id":"AgADAgADqacxG6wpRwABvEB6fpeIcKS4HAIkAATZH_SpyZjzIwdVAAIC","file_size":6150,"width":160,"height":160},{"file_id":"AgADAgADqacxG6wpRwABvEB6fpeIcKS4HAIkAATOiTNi_YoJMghVAAIC","file_size":13363,"width":320,"height":320},{"file_id":"AgADAgADqacxG6wpRwABvEB6fpeIcKS4HAIkAAQW4DyFv0-lhglVAAIC","file_size":28347,"width":640,"height":640},{"file_id":"AgADAgADqacxG6wpRwABvEB6fpeIcKS4HAIkAAT50RvJCg0GQApVAAIC","file_size":33953,"width":800,"height":800}]]}'
|
||||
json_string = r'{"total_count":1,"photos":[[{"file_id":"AgADAgADqacxG6wpRwABvEB6fpeIcKS4HAIkAATZH_SpyZjzIwdVAAIC","file_unique_id": "AQAD_QIfa3QAAyA4BgAB","file_size":6150,"width":160,"height":160},{"file_id":"AgADAgADqacxG6wpRwABvEB6fpeIcKS4HAIkAATOiTNi_YoJMghVAAIC","file_unique_id": "AQAD_QIfa3QAAyA4BgAB","file_size":13363,"width":320,"height":320},{"file_id":"AgADAgADqacxG6wpRwABvEB6fpeIcKS4HAIkAAQW4DyFv0-lhglVAAIC","file_unique_id": "AQAD_QIfa3QAAyA4BgAB","file_size":28347,"width":640,"height":640},{"file_id":"AgADAgADqacxG6wpRwABvEB6fpeIcKS4HAIkAAT50RvJCg0GQApVAAIC","file_unique_id": "AQAD_QIfa3QAAyA4BgAB","file_size":33953,"width":800,"height":800}]]}'
|
||||
upp = types.UserProfilePhotos.de_json(json_string)
|
||||
assert upp.photos[0][0].width == 160
|
||||
assert upp.photos[0][-1].height == 800
|
||||
|
||||
|
||||
def test_json_contact():
|
||||
json_string = r'{"phone_number":"00011111111","first_name":"dd","last_name":"ddl","user_id":8633}'
|
||||
json_string = r'{"phone_number":"00011111111","first_name":"dd","last_name":"ddl","user_id":8633,"vcard":"SomeContactString"}'
|
||||
contact = types.Contact.de_json(json_string)
|
||||
assert contact.first_name == 'dd'
|
||||
assert contact.last_name == 'ddl'
|
||||
|
||||
|
||||
def test_json_voice():
|
||||
json_string = r'{"duration": 0,"mime_type": "audio/ogg","file_id": "AwcccccccDH1JaB7w_gyFjYQxVAg","file_size": 10481}'
|
||||
json_string = r'{"duration": 0,"mime_type": "audio/ogg","file_id": "AwcccccccDH1JaB7w_gyFjYQxVAg","file_unique_id": "AgADbAEAAn8VSFY","file_size": 10481}'
|
||||
voice = types.Voice.de_json(json_string)
|
||||
assert voice.duration == 0
|
||||
assert voice.file_size == 10481
|
||||
|
Reference in New Issue
Block a user