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

Compare commits

...

109 Commits
3.7.3 ... 3.7.6

Author SHA1 Message Date
003a92f466 Merge pull request #1080 from Badiboy/master
Release v.3.7.6
2021-01-18 01:02:45 +03:00
d57aa04bfb Release v.3.7.6 2021-01-18 01:02:19 +03:00
9c2d279806 Merge pull request #1078 from ModischFabrications/patch-1
Hide token from debug logs
2021-01-17 12:21:42 +03:00
3109e35bb4 show bot id 2021-01-17 01:26:38 +01:00
ea51b1e95e hide token from debug logs
prevent leaks of the bot token by hiding it from the log
2021-01-17 01:06:47 +01:00
3799a1e99a Merge pull request #1077 from Badiboy/master
Added short live sessions
2021-01-17 00:46:01 +03:00
ec8714ad3a Short live sessions u1 2021-01-17 00:43:52 +03:00
bc54a5379c Added short live sessions 2021-01-16 23:50:25 +03:00
a7587057bf Merge pull request #1076 from Badiboy/master
Polling timeout fix
2021-01-16 02:15:26 +03:00
e9ba2fd8bb Polling timeout fix 2021-01-16 02:14:29 +03:00
8203fa588f Merge pull request #1074 from Badiboy/master
Version update to previous commit
2021-01-14 15:49:01 +03:00
2e5250ec98 Version update to previous commit 2021-01-14 15:48:30 +03:00
1f910745f1 Merge pull request #1073 from Badiboy/master
Fix restrict_chat_member until_date bug
2021-01-14 15:47:21 +03:00
f56da17741 Fix restrict_chat_member until_date bug 2021-01-14 15:45:47 +03:00
a0d86977b0 Merge pull request #1072 from Badiboy/master
Infinity polling fall down fixed
2021-01-14 03:57:00 +03:00
82838e1d26 Infinity polling fall down fixed 2021-01-14 03:44:37 +03:00
bb8bc7672a Merge pull request #1070 from dgarcoe/patch-1
Update README.md
2021-01-13 20:03:10 +03:00
6e3e159109 Update README.md
I included my bot in the README. Thanks for the library!
2021-01-13 17:55:08 +01:00
87574a7613 Merge pull request #1066 from Badiboy/master
drop_pending_updates in set_webhook
2021-01-09 21:23:55 +03:00
52ebb5a1a7 drop_pending_updates in set_webhook 2021-01-09 21:22:49 +03:00
da9ee5ffba Merge pull request #1065 from Badiboy/master
Release version  3.7.5
2021-01-07 20:47:12 +03:00
0900acfae9 Release version 3.7.5 2021-01-07 20:46:50 +03:00
c6cf615722 Merge pull request #1062 from Badiboy/master
Added timeout to xxx_webhook
2021-01-07 02:38:26 +03:00
5dc008a762 Added timeout to xxx_webhook 2021-01-07 00:13:44 +03:00
bbcd7aa9db Merge pull request #1060 from TarasKindrat/master
Modify RedisHandlerBackend, add argument "password=None" to __init__()
2021-01-06 15:06:24 +03:00
51effdd9a1 Merge pull request #1061 from barbax7/patch-2
Added bot @donamazonbot to the list
2021-01-06 15:05:37 +03:00
0126ba82a5 Added bot @donamazonbot to the list 2021-01-05 17:31:18 +01:00
6b0484b9db Modify RedisHandlerBackend, add argument "password=None" to __init__()
With argument "password=None" in method __init__(), and argument "password" in "self.redis = Redis(host, port, db, password)", will be able to use Redis with password protection, if password is set .
2021-01-05 13:06:14 +02:00
eab36a99e9 Update README.md 2021-01-01 23:14:00 +08:00
28357c8c33 Merge pull request #1056 from Badiboy/master
Bot API update
2020-12-29 19:27:06 +03:00
6559f431b7 Bot API update
Bot API conformance up to 4.4

Added webhook parameters from 5.0
2020-12-29 19:24:41 +03:00
b5d054cf5f Merge pull request #1054 from timgates42/bugfix_typo_performance
docs: fix simple typo, perfomance -> performance
2020-12-25 01:52:17 +03:00
93b86307d9 docs: fix simple typo, perfomance -> performance
There is a small typo in examples/webhook_examples/README.md.

Should read `performance` rather than `perfomance`.
2020-12-25 09:47:40 +11:00
f62d72e2a1 Merge pull request #1053 from Badiboy/master
Avoid dead threads in treaded polling
2020-12-25 00:03:24 +03:00
c4e624d999 Avoid dead threads in treaded polling 2020-12-24 23:55:12 +03:00
fcb3d9b1b3 Merge pull request #1052 from Badiboy/master
Exception if middleware is used but not enabled
2020-12-24 19:57:25 +03:00
2534dc5925 Exception if middleware is used but not enabled. 2020-12-24 19:55:24 +03:00
ded0d257fc Merge pull request #1050 from twistfire92/master
fix restrict_chat_member method
2020-12-22 23:05:43 +03:00
96686e5221 fix restrict_chat_member method 2020-12-22 21:38:38 +03:00
b522053e27 Merge pull request #1043 from vixfwis/master
Add webhook example for Twisted
2020-12-17 16:49:26 +03:00
4e61bc3a8b add short description to example and readme files 2020-12-17 15:34:36 +03:00
487ede7c88 Merge pull request #1046 from Badiboy/master
Fix unban_chat_member in async
2020-12-16 01:58:31 +03:00
4658d2b8da Fix unban_chat_member in async 2020-12-16 01:57:30 +03:00
75a18e5869 add webhook example for Twisted framework 2020-12-15 15:02:41 +03:00
fab2b2d223 Merge pull request #1035 from Badiboy/master
parse_mode = ""
2020-12-09 11:23:09 +03:00
65c3ca58da Update __init__.py
Allow parse_mode = "" to disable default parse mode.
2020-12-09 01:41:07 +03:00
a4d0b685b5 Merge pull request #1026 from Badiboy/master
Bot API 5.0 pinning-unpinning logic post-fix.
2020-11-29 16:11:46 +03:00
6cc80f25d7 Bot API 5.0 pinning-unpinning logic post-fix. 2020-11-29 15:33:39 +03:00
0418818629 Merge pull request #1025 from alexmechanic/master
Bot API 5.0 pinning-unpinning logic update
2020-11-29 15:28:43 +03:00
b9898bbdda Fix 0a2216a22b #2
+ message_id arg of unpin_chat_message() passing to the helper
- removed passing arg to unpin_all_chat_messages()
2020-11-29 15:21:59 +03:00
00c9351f83 Hotfix 0a2216a22b
* message_id made optional as API declares
2020-11-29 15:12:14 +03:00
0a2216a22b Bot API 5.0 pinning-unpinning logic update
+ add unpin_all_chat_messages() (former unpin_chat_message())
* update unpin_chat_message() (add message_id arg)
2020-11-29 14:47:53 +03:00
438cfe4dbd Merge pull request #1022 from Badiboy/master
Version 3.7.4 release
2020-11-20 23:50:39 +03:00
640f398262 Version 3.7.4 release 2020-11-20 23:49:55 +03:00
a9db217c64 Merge pull request #1020 from Badiboy/master
added only_if_banned to unban_chat_member
2020-11-20 15:05:41 +03:00
5824d47590 added only_if_banned to unban_chat_member 2020-11-18 02:22:52 +03:00
0da192aec7 Merge pull request #1014 from Badiboy/master
set_webhook bugfinx
2020-11-11 01:02:21 +03:00
bd27645965 set_webhook bugfinx
set_webhook does not reset allowed_updates for empty list (to default)
2020-11-11 00:32:34 +03:00
2c15cd0996 Merge pull request #1012 from Badiboy/master
Long polling timeouts update
2020-11-07 16:51:10 +03:00
00d125a298 long_polling_timeout update 3 2020-11-07 14:59:45 +03:00
a548374a4d long_polling_timeout update 2 2020-11-07 14:43:17 +03:00
03e1aef70e long_polling_timeout update 1 2020-11-07 14:02:11 +03:00
ece7ca97e0 Merge pull request #1011 from Badiboy/master
Long polling updates and combo content types
2020-11-07 13:02:22 +03:00
7a3fd30f6a Long polling updates and combo content types 2020-11-07 12:52:51 +03:00
1d99cc224f Merge pull request #1005 from Badiboy/master
Animation content_type
2020-11-03 23:26:53 +03:00
fa3ca84d24 Animation content_type
"When you send gif telegram gives you animation and document at same time in update and when you parse that first if is animation and second is document because of this the content_type set document not animation"
2020-11-03 17:46:19 +03:00
42e6d84f13 Merge pull request #1004 from diegop384/patch-1
Update README.md
2020-11-03 17:36:04 +03:00
27461c03af Update README.md
I added my bot
2020-11-03 09:28:31 -05:00
3be51390b1 Merge pull request #997 from Mrsqd/patch-1
Added Frcstbot description
2020-10-30 00:42:04 +03:00
afa88304d7 Added Frcstbot
I've made a weather forecast bot using your API. Can you approve my request to add it, please?
2020-10-29 02:09:47 +03:00
82e79b6ac6 Merge pull request #995 from pinguluk/patch-1
Update README.md
2020-10-28 14:16:03 +03:00
746c71665e Update README.md
linked example/webhook_examples to the directory
2020-10-28 12:31:57 +02:00
37c09406d0 Merge pull request #989 from andvch/patch-1
Fix broken text_mention html formatting
2020-10-15 11:57:38 +03:00
36a3ce62c4 Fix broken text_mention html formatting 2020-10-14 12:06:49 +03:00
6dc8173176 Merge pull request #977 from Badiboy/master
Previous commit bugfix
2020-09-22 01:46:51 +03:00
00c2e9b51c Piece death fix 2020-09-22 01:41:51 +03:00
29711e2425 Merge remote-tracking branch 'upstream/master' 2020-09-22 01:34:55 +03:00
75a5dd1492 Minor bugfix 2020-09-22 01:34:49 +03:00
3ae145f206 Update README.md 2020-09-10 16:22:50 +08:00
5fda52cf5d Merge pull request #970 from meoww-bot/master
UPG: Added the field reply_markup to the Message object
2020-09-07 13:09:46 +03:00
9ab906e60c fix: simplify code
json.loads(button.to_json()) equals to button.to_dict()
2020-09-02 18:09:14 +08:00
698b4371e6 test: Add tests for InlineKeyboardMarkup and ...
Add tests for InlineKeyboardMarkup and InlineKeyboardButton
2020-09-02 10:33:32 +08:00
a803edd09b fix: button in markup should be obj, not json text 2020-09-02 09:25:23 +08:00
32a9e65ecc fix: reply_markup does not change content_type 2020-09-02 09:12:49 +08:00
decad450d0 feat: make InlineKeyboardMarkup JsonDeserializable
feat: make InlineKeyboardMarkup JsonDeserializable, add de_json func to InlineKeyboardMarkup object
2020-09-01 18:13:22 +08:00
630a9a5b2c feat: make InlineKeyboardButton JsonDeserializable
feat: make InlineKeyboardButton JsonDeserializable, add de_json func to InlineKeyboardButton Object
2020-09-01 18:07:45 +08:00
cdae65116b feat: make LoginUrl JsonDeserializable
feat: make LoginUrl JsonDeserializable, add de_json func
2020-09-01 18:03:21 +08:00
6832c33733 feat: Added the field reply_markup to the Message
Added the field `reply_markup` to the Message object
2020-08-31 12:00:56 +00:00
d15cb16bef Merge pull request #969 from ArtemFrantsiian/patch-1
Fix an error with the is_pil_image function
2020-08-29 22:04:36 +03:00
81100f249c Fix an error with the is_pil_image function
When I've tried to send_photo as shown in detailed_example, I got an error: "AttributeError: module 'PIL' has no attribute 'Image'". 
This error was described well here: https://stackoverflow.com/a/11911536/9092263. So in accordance to prescriptions, I've made changes and It works fine for me.

Steps to reproduce:
1. initiate bot via TeleBot constructor
2. call function bot.send_photo(call.message.chat.id, open("some_image.jpg", "rb"))

P.S.
Error Environment: 
- python==3.8.5
- pyTelegramBotAPI==3.7.3
- PIL==7.2.0
2020-08-29 21:57:41 +03:00
79ff9191f3 Merge pull request #967 from meoww-bot/patch-1
Added the field `file_unique_id` (Bot API 4.5)
2020-08-29 11:18:00 +03:00
bdfb793e34 test: Added file_unique_id from Bot API 4.5 2020-08-29 12:07:38 +08:00
e811163b5f UPG: Added the field file_unique_id
Added the field file_unique_id to the objects Animation, Audio, Document, PassportFile, PhotoSize, Sticker, Video, VideoNote, Voice, File and the fields small_file_unique_id and big_file_unique_id to the object ChatPhoto. (Bot API 4.5)
2020-08-29 04:29:02 +08:00
1eb9651894 Merge pull request #963 from Badiboy/master
Minor readme update
2020-08-26 00:53:12 +03:00
309e55845c Minor readme update 2020-08-26 00:51:55 +03:00
2bc5c1a500 Merge pull request #960 from CrafterKolyan/patch-1
Add last_update_id parameter for constructor
2020-08-25 21:58:33 +03:00
7acad2d825 Merge pull request #958 from Badiboy/master
Bot API support checked/updated up to 4.2
2020-08-25 21:49:40 +03:00
5120650774 Move parameter to the end of list 2020-08-25 21:45:30 +03:00
c13f9a7f98 Add last_update_id parameter for constructor 2020-08-25 21:26:28 +03:00
bab9b4077d Bot API support checked/updated up to 4.2 2020-08-25 18:18:51 +03:00
47b9c1d3fb Merge pull request #957 from Badiboy/master
Empty list optimization, Py2 arteacts removed
2020-08-25 16:46:28 +03:00
06ed637f2f Merge remote-tracking branch 'upstream/master' 2020-08-25 16:24:07 +03:00
7bf432170e Merge pull request #956 from Pablo-Davila/master
Append bots to list
2020-08-25 16:23:52 +03:00
8cd18945c5 Append bots to list
TasksListsBot
MyElizaPsychologistBot
2020-08-25 15:45:08 +02:00
cdd48c7aed Empty list optimization, Py2 arteacts removed,
Empty list optimization: None instead of [].

Py2 arteacts removed: no more six moudle used.
2020-08-24 16:02:35 +03:00
513a85cad9 Merge remote-tracking branch 'upstream/master' 2020-08-24 13:00:30 +03:00
17f48916ad Merge remote-tracking branch 'upstream/master' 2020-08-21 17:40:56 +03:00
5b70980bda Resolve conflicts 2020-08-21 17:38:54 +03:00
73487f96c4 Custom exception handler for poll mode
Initial beta of custom exception handler for poll mode.

Use ExceptionHandler class and bot.exception_handler to proceed unhandled exceptions in poll mode.
2020-08-21 17:36:08 +03:00
15 changed files with 762 additions and 329 deletions

View File

@ -4,6 +4,7 @@ python:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
- "pypy3"
install: "pip install -r requirements.txt"
script:

View File

@ -5,6 +5,7 @@
[![PyPi Package Version](https://img.shields.io/pypi/v/pyTelegramBotAPI.svg)](https://pypi.python.org/pypi/pyTelegramBotAPI)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/pyTelegramBotAPI.svg)](https://pypi.python.org/pypi/pyTelegramBotAPI)
[![Build Status](https://travis-ci.org/eternnoir/pyTelegramBotAPI.svg?branch=master)](https://travis-ci.org/eternnoir/pyTelegramBotAPI)
[![PyPi downloads](https://img.shields.io/pypi/dm/pyTelegramBotAPI.svg)](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.

View File

@ -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')

View File

@ -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*

View 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()

View File

@ -1,5 +1,4 @@
py==1.4.29
pytest==3.0.2
requests==2.20.0
six==1.9.0
wheel==0.24.0

View File

@ -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'

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,3 +1,3 @@
# Versions should comply with PEP440.
# This line is parsed in setup.py:
__version__ = '3.7.3'
__version__ = '3.7.6'

View File

@ -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)

View File

@ -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