mirror of
https://github.com/eternnoir/pyTelegramBotAPI.git
synced 2023-08-10 21:12:57 +03:00
Compare commits
155 Commits
Author | SHA1 | Date | |
---|---|---|---|
6e871b8eb1 | |||
f6359bc32c | |||
2bc052ad5a | |||
022ef6a64c | |||
3232811543 | |||
fabcd93dd7 | |||
8053183cb5 | |||
b2b7d90888 | |||
3e9d73c25d | |||
d6501ddc0e | |||
e818e3875d | |||
56cd3971dc | |||
958ca34e9c | |||
f4ef2366b6 | |||
f553960096 | |||
24ef64456b | |||
3e7da0fd18 | |||
2c0f42b363 | |||
1e4a6e2125 | |||
beeb60aab8 | |||
5b942a5b31 | |||
3e4a6cd702 | |||
0e369953cb | |||
911e356930 | |||
554b39a49a | |||
ea16f35432 | |||
81d94687be | |||
4ba4bc18cf | |||
c117ff2d50 | |||
735c224444 | |||
81adfd335e | |||
7ebe589b46 | |||
9c1b19a9e4 | |||
02b886465e | |||
2d89ceb745 | |||
ae8c3252df | |||
7914f71938 | |||
097ba9fec2 | |||
d09d9f0c09 | |||
29c98b0230 | |||
2b1db1f1b3 | |||
fa80b1dba0 | |||
b45db584df | |||
f52ea635e5 | |||
9b56afd569 | |||
6fb10e92e4 | |||
fcf4d91564 | |||
38319871e6 | |||
2d0b092ea4 | |||
060b8c61bb | |||
db2accc2f8 | |||
798fda4c8a | |||
2578e48134 | |||
ac20216a7a | |||
beb5a456eb | |||
41faadd572 | |||
a15016d7d9 | |||
47dd84c441 | |||
c7b360e982 | |||
09041b018f | |||
3a4cf47def | |||
56e4f68a83 | |||
484e7fccbd | |||
791d65e95a | |||
073d7fb6a7 | |||
a6668397e1 | |||
983d626d87 | |||
a4e73a05c6 | |||
30e304ffb5 | |||
430b34c7a2 | |||
b222416fd8 | |||
f8110cd046 | |||
6bc60f4aa9 | |||
b48a445e9f | |||
0b383498eb | |||
2e3b4223a5 | |||
60bb63ab2b | |||
0aa7a8a8f6 | |||
72ed7c1dde | |||
a29c4af2ee | |||
8d8f234138 | |||
491cc05a95 | |||
b2c6077f4d | |||
fb290dc12d | |||
c088fabe6c | |||
a791ff4e46 | |||
e56f134a7c | |||
38c4c21030 | |||
3e33b7f1cb | |||
e381671645 | |||
ce991e9ac3 | |||
3d5415433e | |||
0bfefdf15d | |||
506464e637 | |||
4554cb969f | |||
65cf841015 | |||
0f0ce934dc | |||
bffbe764e5 | |||
c00595e212 | |||
b20f5b359b | |||
558eef78b4 | |||
3f46ce3b7b | |||
69e8edef19 | |||
d3369245c4 | |||
55e9f2095e | |||
7118613ef7 | |||
105d65d5ce | |||
f11bf08ba1 | |||
66598e39fe | |||
4146b50384 | |||
f62d642572 | |||
18f1fd42b0 | |||
07d198aebe | |||
0370a9f277 | |||
22d3ac027a | |||
795f7fff7f | |||
ab6d40a072 | |||
d26923e167 | |||
05aff236c1 | |||
a9ae070256 | |||
63fe6e01d1 | |||
bbafdd1c1d | |||
fe9df2df8c | |||
b0b8623dce | |||
a01e59951a | |||
d5c202abbd | |||
81299ff613 | |||
25bac68309 | |||
a05324bdad | |||
74c4ab2f04 | |||
ab05cb0045 | |||
709eb8cf45 | |||
643cdeceee | |||
2add34c702 | |||
877397a46b | |||
afbc67795a | |||
da5dc20b3a | |||
ed5e5e5077 | |||
9a6ddce8df | |||
db8478d0a4 | |||
20030f47af | |||
f7cf1965cb | |||
aea067f789 | |||
e2c20c1e55 | |||
1209281787 | |||
742f67c85b | |||
e22fcbe3c0 | |||
d3998dfadb | |||
ff54f194ad | |||
f6b967421e | |||
59559199d5 | |||
98784c811e | |||
26e5f3d3a8 | |||
fe1f99abdf | |||
7540a26fb9 |
35
.github/workflows/setup_python.yml
vendored
Normal file
35
.github/workflows/setup_python.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Setup
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the master branch
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
#workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
all-setups:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [ '3.6','3.7','3.8','3.9', 'pypy-3.6', 'pypy-3.7' ] #'pypy-3.8', 'pypy-3.9' NOT SUPPORTED NOW
|
||||
name: ${{ matrix.python-version }} and tests
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: x64
|
||||
- run: |
|
||||
pip3 install -r requirements.txt
|
||||
python setup.py install
|
||||
cd tests && py.test
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -62,3 +62,4 @@ testMain.py
|
||||
|
||||
#VS Code
|
||||
.vscode/
|
||||
.DS_Store
|
||||
|
121
README.md
121
README.md
@ -1,12 +1,17 @@
|
||||
# <p align="center">pyTelegramBotAPI
|
||||
|
||||
<p align="center">A simple, but extensible Python implementation for the <a href="https://core.telegram.org/bots/api">Telegram Bot API</a>.
|
||||
|
||||
[](https://pypi.python.org/pypi/pyTelegramBotAPI)
|
||||
[](https://pypi.python.org/pypi/pyTelegramBotAPI)
|
||||
[](https://travis-ci.org/eternnoir/pyTelegramBotAPI)
|
||||
[](https://pypi.org/project/pyTelegramBotAPI/)
|
||||
|
||||
# <p align="center">pyTelegramBotAPI
|
||||
|
||||
<p align="center">A simple, but extensible Python implementation for the <a href="https://core.telegram.org/bots/api">Telegram Bot API</a>.
|
||||
|
||||
## <p align="center">Supported Bot API version: <a href="https://core.telegram.org/bots/api#june-25-2021">5.3</a>!
|
||||
|
||||
##Contents
|
||||
|
||||
* [Getting started.](#getting-started)
|
||||
* [Writing your first bot](#writing-your-first-bot)
|
||||
* [Prerequisites](#prerequisites)
|
||||
@ -146,7 +151,7 @@ Outlined below are some general use cases of the API.
|
||||
|
||||
#### Message handlers
|
||||
A message handler is a function that is decorated with the `message_handler` decorator of a TeleBot instance. Message handlers consist of one or multiple filters.
|
||||
Each filter much return True for a certain message in order for a message handler to become eligible to handle that message. A message handler is declared in the following way (provided `bot` is an instance of TeleBot):
|
||||
Each filter must return True for a certain message in order for a message handler to become eligible to handle that message. A message handler is declared in the following way (provided `bot` is an instance of TeleBot):
|
||||
```python
|
||||
@bot.message_handler(filters)
|
||||
def function_name(message):
|
||||
@ -208,26 +213,59 @@ def send_something(message):
|
||||
**Important: all handlers are tested in the order in which they were declared**
|
||||
|
||||
#### Edited Message handlers
|
||||
|
||||
@bot.edited_message_handler(filters)
|
||||
Handle edited messages
|
||||
`@bot.edited_message_handler(filters) # <- passes a Message type object to your function`
|
||||
|
||||
#### channel_post_handler
|
||||
|
||||
@bot.channel_post_handler(filters)
|
||||
Handle channel post messages
|
||||
`@bot.channel_post_handler(filters) # <- passes a Message type object to your function`
|
||||
|
||||
#### edited_channel_post_handler
|
||||
|
||||
@bot.edited_channel_post_handler(filters)
|
||||
Handle edited channel post messages
|
||||
`@bot.edited_channel_post_handler(filters) # <- passes a Message type object to your function`
|
||||
|
||||
#### Callback Query Handler
|
||||
|
||||
In bot2.0 update. You can get `callback_query` in update object. In telebot use `callback_query_handler` to process callback queries.
|
||||
|
||||
Handle callback queries
|
||||
```python
|
||||
@bot.callback_query_handler(func=lambda call: True)
|
||||
def test_callback(call):
|
||||
def test_callback(call): # <- passes a CallbackQuery type object to your function
|
||||
logger.info(call)
|
||||
```
|
||||
|
||||
#### Inline Handler
|
||||
Handle inline queries
|
||||
`@bot.inline_handler() # <- passes a InlineQuery type object to your function`
|
||||
|
||||
#### Chosen Inline Handler
|
||||
Handle chosen inline results
|
||||
`@bot.chosen_inline_handler() # <- passes a ChosenInlineResult type object to your function`
|
||||
|
||||
#### Shipping Query Handler
|
||||
Handle shipping queries
|
||||
`@bot.shipping_query_handeler() # <- passes a ShippingQuery type object to your function`
|
||||
|
||||
#### Pre Checkout Query Handler
|
||||
Handle pre checkoupt queries
|
||||
`@bot.pre_checkout_query_handler() # <- passes a PreCheckoutQuery type object to your function`
|
||||
|
||||
#### Poll Handler
|
||||
Handle poll updates
|
||||
`@bot.poll_handler() # <- passes a Poll type object to your function`
|
||||
|
||||
#### Poll Answer Handler
|
||||
Handle poll answers
|
||||
`@bot.poll_answer_handler() # <- passes a PollAnswer type object to your function`
|
||||
|
||||
#### My Chat Member Handler
|
||||
Handle updates of a the bot's member status in a chat
|
||||
`@bot.my_chat_member_handler() # <- passes a ChatMemberUpdated type object to your function`
|
||||
|
||||
#### Chat Member Handler
|
||||
Handle updates of a chat member's status in a chat
|
||||
`@bot.chat_member_handler() # <- passes a ChatMemberUpdated type object to your function`
|
||||
*Note: "chat_member" updates are not requested by default. If you want to allow all update types, set `allowed_updates` in `bot.polling()` / `bot.infinity_polling()` to `util.update_types`*
|
||||
|
||||
|
||||
#### Middleware Handler
|
||||
|
||||
A middleware handler is a function that allows you to modify requests or the bot context as they pass through the
|
||||
@ -261,6 +299,7 @@ tb = telebot.TeleBot(TOKEN) #create a new Telegram Bot object
|
||||
# - interval: True/False (default False) - The interval between polling requests
|
||||
# Note: Editing this parameter harms the bot's response time
|
||||
# - timeout: integer (default 20) - Timeout in seconds for long polling.
|
||||
# - allowed_updates: List of Strings (default None) - List of update types to request
|
||||
tb.polling(none_stop=False, interval=0, timeout=20)
|
||||
|
||||
# getMe
|
||||
@ -454,8 +493,20 @@ Refer [Bot Api](https://core.telegram.org/bots/api#messageentity) for extra deta
|
||||
|
||||
## Advanced use of the API
|
||||
|
||||
### Using local Bot API Sever
|
||||
Since version 5.0 of the Bot API, you have the possibility to run your own [Local Bot API Server](https://core.telegram.org/bots/api#using-a-local-bot-api-server).
|
||||
pyTelegramBotAPI also supports this feature.
|
||||
```python
|
||||
from telebot import apihelper
|
||||
|
||||
apihelper.API_URL = "http://localhost:4200/bot{0}/{1}"
|
||||
```
|
||||
**Important: Like described [here](https://core.telegram.org/bots/api#logout), you have to log out your bot from the Telegram server before switching to your local API server. in pyTelegramBotAPI use `bot.log_out()`**
|
||||
|
||||
*Note: 4200 is an example port*
|
||||
|
||||
### Asynchronous delivery of messages
|
||||
There exists an implementation of TeleBot which executes all `send_xyz` and the `get_me` functions asynchronously. This can speed up you bot __significantly__, but it has unwanted side effects if used without caution.
|
||||
There exists an implementation of TeleBot which executes all `send_xyz` and the `get_me` functions asynchronously. This can speed up your bot __significantly__, but it has unwanted side effects if used without caution.
|
||||
To enable this behaviour, create an instance of AsyncTeleBot instead of TeleBot.
|
||||
```python
|
||||
tb = telebot.AsyncTeleBot("TOKEN")
|
||||
@ -484,6 +535,19 @@ large_text = open("large_text.txt", "rb").read()
|
||||
# Split the text each 3000 characters.
|
||||
# split_string returns a list with the splitted text.
|
||||
splitted_text = util.split_string(large_text, 3000)
|
||||
|
||||
for text in splitted_text:
|
||||
tb.send_message(chat_id, text)
|
||||
```
|
||||
|
||||
Or you can use the new `smart_split` function to get more meaningful substrings:
|
||||
```python
|
||||
from telebot import util
|
||||
large_text = open("large_text.txt", "rb").read()
|
||||
# Splits one string into multiple strings, with a maximum amount of `chars_per_string` (max. 4096)
|
||||
# Splits by last '\n', '. ' or ' ' in exactly this priority.
|
||||
# smart_split returns a list with the splitted text.
|
||||
splitted_text = util.smart_split(large_text, chars_per_string=3000)
|
||||
for text in splitted_text:
|
||||
tb.send_message(chat_id, text)
|
||||
```
|
||||
@ -532,7 +596,7 @@ You can use proxy for request. `apihelper.proxy` object will use by call `reques
|
||||
```python
|
||||
from telebot import apihelper
|
||||
|
||||
apihelper.proxy = {'http':'http://10.10.1.10:3128'}
|
||||
apihelper.proxy = {'http':'http://127.0.0.1:3128'}
|
||||
```
|
||||
|
||||
If you want to use socket5 proxy you need install dependency `pip install requests[socks]` and make sure, that you have the latest version of `gunicorn`, `PySocks`, `pyTelegramBotAPI`, `requests` and `urllib3`.
|
||||
@ -544,15 +608,20 @@ apihelper.proxy = {'https':'socks5://userproxy:password@proxy_address:port'}
|
||||
|
||||
## API conformance
|
||||
|
||||
_Checking is in progress..._
|
||||
|
||||
✅ [Bot API 4.5](https://core.telegram.org/bots/api-changelog#december-31-2019) _- To be checked..._
|
||||
|
||||
* ➕ [Bot API 5.3](https://core.telegram.org/bots/api#june-25-2021) - ChatMemberXXX classes are full copies of ChatMember
|
||||
* ✔ [Bot API 5.2](https://core.telegram.org/bots/api#april-26-2021)
|
||||
* ✔ [Bot API 5.1](https://core.telegram.org/bots/api#march-9-2021)
|
||||
* ✔ [Bot API 5.0](https://core.telegram.org/bots/api-changelog#november-4-2020)
|
||||
* ✔ [Bot API 4.9](https://core.telegram.org/bots/api-changelog#june-4-2020)
|
||||
* ✔ [Bot API 4.8](https://core.telegram.org/bots/api-changelog#april-24-2020)
|
||||
* ✔ [Bot API 4.7](https://core.telegram.org/bots/api-changelog#march-30-2020)
|
||||
* ✔ [Bot API 4.6](https://core.telegram.org/bots/api-changelog#january-23-2020)
|
||||
* ➕ [Bot API 4.5](https://core.telegram.org/bots/api-changelog#december-31-2019) - No nested MessageEntities and Markdown2 support
|
||||
* ✔ [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 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)
|
||||
@ -632,6 +701,7 @@ Get help. Discuss. Chat.
|
||||
* [League of Legends bot](https://telegram.me/League_of_Legends_bot) ([source](https://github.com/i32ropie/lol)) by *i32ropie*
|
||||
* [NeoBot](https://github.com/neoranger/NeoBot) by [@NeoRanger](https://github.com/neoranger)
|
||||
* [TagAlertBot](https://github.com/pitasi/TagAlertBot) by *pitasi*
|
||||
* [ColorCodeBot](https://t.me/colorcodebot) ([source](https://github.com/andydecleyre/colorcodebot)) - Share code snippets as beautifully syntax-highlighted HTML and/or images.
|
||||
* [ComedoresUGRbot](http://telegram.me/ComedoresUGRbot) ([source](https://github.com/alejandrocq/ComedoresUGRbot)) by [*alejandrocq*](https://github.com/alejandrocq) - Telegram bot to check the menu of Universidad de Granada dining hall.
|
||||
* [picpingbot](https://web.telegram.org/#/im?p=%40picpingbot) - Fun anonymous photo exchange by Boogie Muffin.
|
||||
* [TheZigZagProject](https://github.com/WebShark025/TheZigZagProject) - The 'All In One' bot for Telegram! by WebShark025
|
||||
@ -685,5 +755,10 @@ Get help. Discuss. Chat.
|
||||
* [BarnameKon](https://t.me/BarnameKonBot) by [Anvaari](https://github.com/anvaari). This Bot make "Add to google calendar" link for your events. It give information about event and return link. It work for Jalali calendar and in Tehran Time. [Source code](https://github.com/anvaari/BarnameKon)
|
||||
* [Price Tracker](https://t.me/trackokbot) by [@barbax7](https://github.com/barbax7). This bot tracks amazon.it product's prices the user is interested to and notify him when one price go down.
|
||||
* [Torrent Hunt](https://t.me/torrenthuntbot) by [@Hemantapkh](https://github.com/hemantapkh/torrenthunt). Torrent Hunt bot is a multilingual bot with inline mode support to search and explore torrents from 1337x.to.
|
||||
|
||||
* Translator bot by [Areeg Fahad (source)](https://github.com/AREEG94FAHAD/translate_text_bot). This bot can be use to translate texts.
|
||||
* Digital Cryptocurrency bot by [Areeg Fahad (source)](https://github.com/AREEG94FAHAD/currencies_bot). With this bot, you can now monitor the prices of more than 12 digital Cryptocurrency.
|
||||
* [Anti-Tracking Bot](https://t.me/AntiTrackingBot) by [Leon Heess (source)](https://github.com/leonheess/AntiTrackingBot). Send any link, and the bot tries its best to remove all tracking from the link you sent.
|
||||
* [Developer Bot](https://t.me/IndDeveloper_bot) by [Vishal Singh](https://github.com/vishal2376) [(source code)](https://github.com/vishal2376/telegram-bot) This telegram bot can do tasks like GitHub search & clone,provide c++ learning resources ,Stackoverflow search, Codeforces(profile visualizer,random problems)
|
||||
* [oneIPO bot](https://github.com/aaditya2200/IPO-proj) by [Aadithya](https://github.com/aaditya2200) & [Amol Soans](https://github.com/AmolDerickSoans) This Telegram bot provides live updates , data and documents on current and upcoming IPOs(Initial Public Offerings)
|
||||
|
||||
**Want to have your bot listed here? Just make a pull request.**
|
||||
|
33
examples/chat_member_example.py
Normal file
33
examples/chat_member_example.py
Normal file
@ -0,0 +1,33 @@
|
||||
import telebot
|
||||
from telebot import types,util
|
||||
|
||||
bot = telebot.TeleBot("token")
|
||||
|
||||
#chat_member_handler. When status changes, telegram gives update. check status from old_chat_member and new_chat_member.
|
||||
@bot.chat_member_handler()
|
||||
def chat_m(message: types.ChatMemberUpdated):
|
||||
old = message.old_chat_member
|
||||
new = message.new_chat_member
|
||||
if new.status == "member":
|
||||
bot.send_message(message.chat.id,"Hello {name}!".format(name=new.user.first_name)) # Welcome message
|
||||
|
||||
#if bot is added to group, this handler will work
|
||||
@bot.my_chat_member_handler()
|
||||
def my_chat_m(message: types.ChatMemberUpdated):
|
||||
old = message.old_chat_member
|
||||
new = message.new_chat_member
|
||||
if new.status == "member":
|
||||
bot.send_message(message.chat.id,"Somebody added me to group") # Welcome message, if bot was added to group
|
||||
bot.leave_chat(message.chat.id)
|
||||
|
||||
#content_Type_service is:
|
||||
#'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',
|
||||
#'proximity_alert_triggered', 'voice_chat_scheduled', 'voice_chat_started', 'voice_chat_ended',
|
||||
#'voice_chat_participants_invited', 'message_auto_delete_timer_changed'
|
||||
# this handler deletes service messages
|
||||
|
||||
@bot.message_handler(content_types=util.content_type_service)
|
||||
def delall(message: types.Message):
|
||||
bot.delete_message(message.chat.id,message.message_id)
|
||||
bot.polling(allowed_updates=util.update_types)
|
13
examples/skip_updates_example.py
Normal file
13
examples/skip_updates_example.py
Normal file
@ -0,0 +1,13 @@
|
||||
import telebot
|
||||
|
||||
bot = telebot.TeleBot("TOKEN")
|
||||
|
||||
@bot.message_handler(commands=['start', 'help'])
|
||||
def send_welcome(message):
|
||||
bot.reply_to(message, "Howdy, how are you doing?")
|
||||
|
||||
@bot.message_handler(func=lambda message: True)
|
||||
def echo_all(message):
|
||||
bot.reply_to(message, message.text)
|
||||
|
||||
bot.polling(skip_pending=True)# Skip pending skips old updates
|
@ -1,4 +1,3 @@
|
||||
py==1.10.0
|
||||
pytest==3.0.2
|
||||
requests==2.20.0
|
||||
wheel==0.24.0
|
||||
|
1
setup.py
1
setup.py
@ -25,6 +25,7 @@ setup(name='pyTelegramBotAPI',
|
||||
install_requires=['requests'],
|
||||
extras_require={
|
||||
'json': 'ujson',
|
||||
'PIL': 'Pillow',
|
||||
'redis': 'redis>=3.4.1'
|
||||
},
|
||||
classifiers=[
|
||||
|
1442
telebot/__init__.py
1442
telebot/__init__.py
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ import requests
|
||||
from requests.exceptions import HTTPError, ConnectionError, Timeout
|
||||
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from requests.packages.urllib3 import fields
|
||||
format_header_param = fields.format_header_param
|
||||
except ImportError:
|
||||
@ -27,8 +28,11 @@ session = None
|
||||
API_URL = None
|
||||
FILE_URL = None
|
||||
|
||||
CONNECT_TIMEOUT = 3.5
|
||||
READ_TIMEOUT = 9999
|
||||
CONNECT_TIMEOUT = 15
|
||||
READ_TIMEOUT = 30
|
||||
|
||||
LONG_POLLING_TIMEOUT = 10 # Should be positive, short polling should be used for testing purposes only (https://core.telegram.org/bots/api#getupdates)
|
||||
|
||||
SESSION_TIME_TO_LIVE = None # In seconds. None - live forever, 0 - one-time
|
||||
|
||||
RETRY_ON_ERROR = False
|
||||
@ -44,6 +48,7 @@ def _get_req_session(reset=False):
|
||||
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)
|
||||
# noinspection PyTypeChecker
|
||||
if (datetime.now() - creation_date).total_seconds() > SESSION_TIME_TO_LIVE:
|
||||
# Force session reset
|
||||
reset = True
|
||||
@ -68,7 +73,10 @@ 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 not token:
|
||||
raise Exception('Bot token is not defined')
|
||||
if API_URL:
|
||||
# noinspection PyUnresolvedReferences
|
||||
request_url = API_URL.format(token, method_name)
|
||||
else:
|
||||
request_url = "https://api.telegram.org/bot{0}/{1}".format(token, method_name)
|
||||
@ -80,16 +88,21 @@ def _make_request(token, method_name, method='get', params=None, files=None):
|
||||
fields.format_header_param = _no_encode(format_header_param)
|
||||
if params:
|
||||
if 'timeout' in params:
|
||||
read_timeout = params.pop('timeout') + 10
|
||||
if 'connect-timeout' in params:
|
||||
connect_timeout = params.pop('connect-timeout') + 10
|
||||
read_timeout = params.pop('timeout')
|
||||
connect_timeout = read_timeout
|
||||
# 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)
|
||||
# For getUpdates: it's the only function with timeout parameter on the BOT API side
|
||||
long_polling_timeout = params.pop('long_polling_timeout')
|
||||
params['timeout'] = long_polling_timeout
|
||||
# Long polling hangs for a given time. Read timeout should be greater that long_polling_timeout
|
||||
read_timeout = max(long_polling_timeout + 5, read_timeout)
|
||||
# Lets stop suppose that user is stupid and assume that he knows what he do...
|
||||
# read_timeout = read_timeout + 10
|
||||
# connect_timeout = connect_timeout + 10
|
||||
|
||||
params = params or None # Set params to None if empty
|
||||
|
||||
result = None
|
||||
if RETRY_ON_ERROR:
|
||||
@ -166,6 +179,16 @@ def get_me(token):
|
||||
return _make_request(token, method_url)
|
||||
|
||||
|
||||
def log_out(token):
|
||||
method_url = r'logOut'
|
||||
return _make_request(token, method_url)
|
||||
|
||||
|
||||
def close(token):
|
||||
method_url = r'close'
|
||||
return _make_request(token, method_url)
|
||||
|
||||
|
||||
def get_file(token, file_id):
|
||||
method_url = r'getFile'
|
||||
return _make_request(token, method_url, params={'file_id': file_id})
|
||||
@ -175,13 +198,15 @@ def get_file_url(token, file_id):
|
||||
if FILE_URL is None:
|
||||
return "https://api.telegram.org/file/bot{0}/{1}".format(token, get_file(token, file_id)['file_path'])
|
||||
else:
|
||||
# noinspection PyUnresolvedReferences
|
||||
return FILE_URL.format(token, get_file(token, file_id)['file_path'])
|
||||
|
||||
|
||||
|
||||
def download_file(token, file_path):
|
||||
if FILE_URL is None:
|
||||
url = "https://api.telegram.org/file/bot{0}/{1}".format(token, file_path)
|
||||
else:
|
||||
# noinspection PyUnresolvedReferences
|
||||
url = FILE_URL.format(token, file_path)
|
||||
|
||||
result = _get_req_session().get(url, proxies=proxy)
|
||||
@ -194,7 +219,8 @@ def download_file(token, file_path):
|
||||
def send_message(
|
||||
token, chat_id, text,
|
||||
disable_web_page_preview=None, reply_to_message_id=None, reply_markup=None,
|
||||
parse_mode=None, disable_notification=None, timeout=None):
|
||||
parse_mode=None, disable_notification=None, timeout=None,
|
||||
entities=None, allow_sending_without_reply=None):
|
||||
"""
|
||||
Use this method to send text messages. On success, the sent Message is returned.
|
||||
:param token:
|
||||
@ -206,6 +232,8 @@ def send_message(
|
||||
:param parse_mode:
|
||||
:param disable_notification:
|
||||
:param timeout:
|
||||
:param entities:
|
||||
:param allow_sending_without_reply:
|
||||
:return:
|
||||
"""
|
||||
method_url = r'sendMessage'
|
||||
@ -221,7 +249,11 @@ def send_message(
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if entities:
|
||||
payload['entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(entities))
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -243,7 +275,7 @@ def set_webhook(token, url=None, certificate=None, max_connections=None, allowed
|
||||
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
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload, files=files)
|
||||
|
||||
|
||||
@ -253,7 +285,7 @@ def delete_webhook(token, drop_pending_updates=None, timeout=None):
|
||||
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
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -261,7 +293,7 @@ def get_webhook_info(token, timeout=None):
|
||||
method_url = r'getWebhookInfo'
|
||||
payload = {}
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -273,9 +305,8 @@ def get_updates(token, offset=None, limit=None, timeout=None, allowed_updates=No
|
||||
if limit:
|
||||
payload['limit'] = limit
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
if long_polling_timeout:
|
||||
payload['long_polling_timeout'] = long_polling_timeout
|
||||
payload['timeout'] = timeout
|
||||
payload['long_polling_timeout'] = long_polling_timeout if long_polling_timeout else 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)
|
||||
@ -309,12 +340,24 @@ def get_chat_administrators(token, chat_id):
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def get_chat_members_count(token, chat_id):
|
||||
method_url = r'getChatMembersCount'
|
||||
def get_chat_member_count(token, chat_id):
|
||||
method_url = r'getChatMemberCount'
|
||||
payload = {'chat_id': chat_id}
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def set_sticker_set_thumb(token, name, user_id, thumb):
|
||||
method_url = r'setStickerSetThumb'
|
||||
payload = {'name': name, 'user_id': user_id}
|
||||
files = {}
|
||||
if thumb:
|
||||
if not isinstance(thumb, str):
|
||||
files['thumb'] = thumb
|
||||
else:
|
||||
payload['thumb'] = thumb
|
||||
return _make_request(token, method_url, params=payload, files=files or None)
|
||||
|
||||
|
||||
def set_chat_sticker_set(token, chat_id, sticker_set_name):
|
||||
method_url = r'setChatStickerSet'
|
||||
payload = {'chat_id': chat_id, 'sticker_set_name': sticker_set_name}
|
||||
@ -341,7 +384,7 @@ def forward_message(
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -365,14 +408,14 @@ def copy_message(token, chat_id, from_chat_id, message_id, caption=None, parse_m
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def send_dice(
|
||||
token, chat_id,
|
||||
emoji=None, disable_notification=None, reply_to_message_id=None,
|
||||
reply_markup=None, timeout=None):
|
||||
reply_markup=None, timeout=None, allow_sending_without_reply=None):
|
||||
method_url = r'sendDice'
|
||||
payload = {'chat_id': chat_id}
|
||||
if emoji:
|
||||
@ -384,14 +427,17 @@ def send_dice(
|
||||
if reply_markup:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def send_photo(
|
||||
token, chat_id, photo,
|
||||
caption=None, reply_to_message_id=None, reply_markup=None,
|
||||
parse_mode=None, disable_notification=None, timeout=None):
|
||||
parse_mode=None, disable_notification=None, timeout=None,
|
||||
caption_entities=None, allow_sending_without_reply=None):
|
||||
method_url = r'sendPhoto'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -412,14 +458,18 @@ def send_photo(
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if caption_entities:
|
||||
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
|
||||
|
||||
def send_media_group(
|
||||
token, chat_id, media,
|
||||
disable_notification=None, reply_to_message_id=None,
|
||||
timeout=None):
|
||||
timeout=None, allow_sending_without_reply=None):
|
||||
method_url = r'sendMediaGroup'
|
||||
media_json, files = convert_input_media_array(media)
|
||||
payload = {'chat_id': chat_id, 'media': media_json}
|
||||
@ -428,7 +478,9 @@ def send_media_group(
|
||||
if reply_to_message_id:
|
||||
payload['reply_to_message_id'] = reply_to_message_id
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(
|
||||
token, method_url, params=payload,
|
||||
method='post' if files else 'get',
|
||||
@ -437,37 +489,55 @@ def send_media_group(
|
||||
|
||||
def send_location(
|
||||
token, chat_id, latitude, longitude,
|
||||
live_period=None, reply_to_message_id=None, reply_markup=None,
|
||||
disable_notification=None, timeout=None):
|
||||
live_period=None, reply_to_message_id=None,
|
||||
reply_markup=None, disable_notification=None,
|
||||
timeout=None, horizontal_accuracy=None, heading=None,
|
||||
proximity_alert_radius=None, allow_sending_without_reply=None):
|
||||
method_url = r'sendLocation'
|
||||
payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude}
|
||||
if live_period:
|
||||
payload['live_period'] = live_period
|
||||
if horizontal_accuracy:
|
||||
payload['horizontal_accuracy'] = horizontal_accuracy
|
||||
if heading:
|
||||
payload['heading'] = heading
|
||||
if proximity_alert_radius:
|
||||
payload['proximity_alert_radius'] = proximity_alert_radius
|
||||
if reply_to_message_id:
|
||||
payload['reply_to_message_id'] = reply_to_message_id
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if reply_markup:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def edit_message_live_location(token, latitude, longitude, chat_id=None, message_id=None,
|
||||
inline_message_id=None, reply_markup=None, timeout=None):
|
||||
def edit_message_live_location(
|
||||
token, latitude, longitude, chat_id=None, message_id=None,
|
||||
inline_message_id=None, reply_markup=None, timeout=None,
|
||||
horizontal_accuracy=None, heading=None, proximity_alert_radius=None):
|
||||
method_url = r'editMessageLiveLocation'
|
||||
payload = {'latitude': latitude, 'longitude': longitude}
|
||||
if chat_id:
|
||||
payload['chat_id'] = chat_id
|
||||
if message_id:
|
||||
payload['message_id'] = message_id
|
||||
if horizontal_accuracy:
|
||||
payload['horizontal_accuracy'] = horizontal_accuracy
|
||||
if heading:
|
||||
payload['heading'] = heading
|
||||
if proximity_alert_radius:
|
||||
payload['proximity_alert_radius'] = proximity_alert_radius
|
||||
if inline_message_id:
|
||||
payload['inline_message_id'] = inline_message_id
|
||||
if reply_markup:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -485,14 +555,16 @@ def stop_message_live_location(
|
||||
if reply_markup:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def send_venue(
|
||||
token, 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):
|
||||
reply_to_message_id=None, reply_markup=None, timeout=None,
|
||||
allow_sending_without_reply=None, google_place_id=None,
|
||||
google_place_type=None):
|
||||
method_url = r'sendVenue'
|
||||
payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude, 'title': title, 'address': address}
|
||||
if foursquare_id:
|
||||
@ -506,13 +578,20 @@ def send_venue(
|
||||
if reply_markup:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if google_place_id:
|
||||
payload['google_place_id'] = google_place_id
|
||||
if google_place_type:
|
||||
payload['google_place_type'] = google_place_type
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def send_contact(
|
||||
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):
|
||||
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None,
|
||||
allow_sending_without_reply=None):
|
||||
method_url = r'sendContact'
|
||||
payload = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name}
|
||||
if last_name:
|
||||
@ -526,7 +605,9 @@ def send_contact(
|
||||
if reply_markup:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -534,12 +615,13 @@ def send_chat_action(token, chat_id, action, timeout=None):
|
||||
method_url = r'sendChatAction'
|
||||
payload = {'chat_id': chat_id, 'action': action}
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def send_video(token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None,
|
||||
parse_mode=None, supports_streaming=None, disable_notification=None, timeout=None, thumb=None, width=None, height=None):
|
||||
parse_mode=None, supports_streaming=None, disable_notification=None, timeout=None,
|
||||
thumb=None, width=None, height=None, caption_entities=None, allow_sending_without_reply=None):
|
||||
method_url = r'sendVideo'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -562,7 +644,7 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_to_messa
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if thumb:
|
||||
if not util.is_string(thumb):
|
||||
if files:
|
||||
@ -575,11 +657,17 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_to_messa
|
||||
payload['width'] = width
|
||||
if height:
|
||||
payload['height'] = height
|
||||
if caption_entities:
|
||||
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
|
||||
|
||||
def send_animation(token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None,
|
||||
parse_mode=None, disable_notification=None, timeout=None, thumb=None):
|
||||
def send_animation(
|
||||
token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None,
|
||||
parse_mode=None, disable_notification=None, timeout=None, thumb=None, caption_entities=None,
|
||||
allow_sending_without_reply=None):
|
||||
method_url = r'sendAnimation'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -600,7 +688,7 @@ def send_animation(token, chat_id, data, duration=None, caption=None, reply_to_m
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if thumb:
|
||||
if not util.is_string(thumb):
|
||||
if files:
|
||||
@ -609,11 +697,16 @@ def send_animation(token, chat_id, data, duration=None, caption=None, reply_to_m
|
||||
files = {'thumb': thumb}
|
||||
else:
|
||||
payload['thumb'] = thumb
|
||||
if caption_entities:
|
||||
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
|
||||
|
||||
def send_voice(token, chat_id, voice, caption=None, duration=None, reply_to_message_id=None, reply_markup=None,
|
||||
parse_mode=None, disable_notification=None, timeout=None):
|
||||
parse_mode=None, disable_notification=None, timeout=None, caption_entities=None,
|
||||
allow_sending_without_reply=None):
|
||||
method_url = r'sendVoice'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -634,12 +727,16 @@ def send_voice(token, chat_id, voice, caption=None, duration=None, reply_to_mess
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if caption_entities:
|
||||
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
|
||||
|
||||
def send_video_note(token, chat_id, data, duration=None, length=None, reply_to_message_id=None, reply_markup=None,
|
||||
disable_notification=None, timeout=None, thumb=None):
|
||||
disable_notification=None, timeout=None, thumb=None, allow_sending_without_reply=None):
|
||||
method_url = r'sendVideoNote'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -660,7 +757,7 @@ def send_video_note(token, chat_id, data, duration=None, length=None, reply_to_m
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if thumb:
|
||||
if not util.is_string(thumb):
|
||||
if files:
|
||||
@ -669,11 +766,14 @@ def send_video_note(token, chat_id, data, duration=None, length=None, reply_to_m
|
||||
files = {'thumb': thumb}
|
||||
else:
|
||||
payload['thumb'] = thumb
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
|
||||
|
||||
def send_audio(token, chat_id, audio, caption=None, duration=None, performer=None, title=None, reply_to_message_id=None,
|
||||
reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, thumb=None):
|
||||
reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, thumb=None,
|
||||
caption_entities=None, allow_sending_without_reply=None):
|
||||
method_url = r'sendAudio'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -698,7 +798,7 @@ def send_audio(token, chat_id, audio, caption=None, duration=None, performer=Non
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if thumb:
|
||||
if not util.is_string(thumb):
|
||||
if files:
|
||||
@ -707,16 +807,24 @@ def send_audio(token, chat_id, audio, caption=None, duration=None, performer=Non
|
||||
files = {'thumb': thumb}
|
||||
else:
|
||||
payload['thumb'] = thumb
|
||||
if caption_entities:
|
||||
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
|
||||
|
||||
def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_markup=None, parse_mode=None,
|
||||
disable_notification=None, timeout=None, caption=None, thumb=None):
|
||||
disable_notification=None, timeout=None, caption=None, thumb=None, caption_entities=None,
|
||||
allow_sending_without_reply=None, disable_content_type_detection=None, visible_file_name=None):
|
||||
method_url = get_method_by_type(data_type)
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
if not util.is_string(data):
|
||||
files = {data_type: data}
|
||||
file_data = data
|
||||
if visible_file_name:
|
||||
file_data = (visible_file_name, data)
|
||||
files = {data_type: file_data}
|
||||
else:
|
||||
payload[data_type] = data
|
||||
if reply_to_message_id:
|
||||
@ -728,7 +836,7 @@ def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_m
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if caption:
|
||||
payload['caption'] = caption
|
||||
if thumb:
|
||||
@ -739,6 +847,12 @@ def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_m
|
||||
files = {'thumb': thumb}
|
||||
else:
|
||||
payload['thumb'] = thumb
|
||||
if caption_entities:
|
||||
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if method_url == 'sendDocument' and disable_content_type_detection is not None:
|
||||
payload['disable_content_type_detection'] = disable_content_type_detection
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
|
||||
|
||||
@ -749,13 +863,15 @@ def get_method_by_type(data_type):
|
||||
return r'sendSticker'
|
||||
|
||||
|
||||
def kick_chat_member(token, chat_id, user_id, until_date=None):
|
||||
method_url = 'kickChatMember'
|
||||
def ban_chat_member(token, chat_id, user_id, until_date=None, revoke_messages=None):
|
||||
method_url = 'banChatMember'
|
||||
payload = {'chat_id': chat_id, 'user_id': user_id}
|
||||
if isinstance(until_date, datetime):
|
||||
payload['until_date'] = until_date.timestamp()
|
||||
else:
|
||||
payload['until_date'] = until_date
|
||||
if revoke_messages is not None:
|
||||
payload['revoke_messages'] = revoke_messages
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -804,7 +920,8 @@ def restrict_chat_member(
|
||||
def promote_chat_member(
|
||||
token, chat_id, user_id, can_change_info=None, can_post_messages=None,
|
||||
can_edit_messages=None, can_delete_messages=None, can_invite_users=None,
|
||||
can_restrict_members=None, can_pin_messages=None, can_promote_members=None):
|
||||
can_restrict_members=None, can_pin_messages=None, can_promote_members=None,
|
||||
is_anonymous=None, can_manage_chat=None, can_manage_voice_chats=None):
|
||||
method_url = 'promoteChatMember'
|
||||
payload = {'chat_id': chat_id, 'user_id': user_id}
|
||||
if can_change_info is not None:
|
||||
@ -823,6 +940,12 @@ def promote_chat_member(
|
||||
payload['can_pin_messages'] = can_pin_messages
|
||||
if can_promote_members is not None:
|
||||
payload['can_promote_members'] = can_promote_members
|
||||
if is_anonymous is not None:
|
||||
payload['is_anonymous'] = is_anonymous
|
||||
if can_manage_chat is not None:
|
||||
payload['can_manage_chat'] = can_manage_chat
|
||||
if can_manage_voice_chats is not None:
|
||||
payload['can_manage_voice_chats'] = can_manage_voice_chats
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -843,6 +966,51 @@ def set_chat_permissions(token, chat_id, permissions):
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def create_chat_invite_link(token, chat_id, expire_date, member_limit):
|
||||
method_url = 'createChatInviteLink'
|
||||
payload = {
|
||||
'chat_id': chat_id
|
||||
}
|
||||
|
||||
if expire_date is not None:
|
||||
if isinstance(payload['expire_date'], datetime):
|
||||
payload['expire_date'] = payload['expire_date'].timestamp()
|
||||
else:
|
||||
payload['expire_date'] = expire_date
|
||||
if member_limit:
|
||||
payload['member_limit'] = member_limit
|
||||
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def edit_chat_invite_link(token, chat_id, invite_link, expire_date, member_limit):
|
||||
method_url = 'editChatInviteLink'
|
||||
payload = {
|
||||
'chat_id': chat_id,
|
||||
'invite_link': invite_link
|
||||
}
|
||||
|
||||
if expire_date is not None:
|
||||
if isinstance(payload['expire_date'], datetime):
|
||||
payload['expire_date'] = payload['expire_date'].timestamp()
|
||||
else:
|
||||
payload['expire_date'] = expire_date
|
||||
|
||||
if member_limit is not None:
|
||||
payload['member_limit'] = member_limit
|
||||
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def revoke_chat_invite_link(token, chat_id, invite_link):
|
||||
method_url = 'revokeChatInviteLink'
|
||||
payload = {
|
||||
'chat_id': chat_id,
|
||||
'invite_link': invite_link
|
||||
}
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def export_chat_invite_link(token, chat_id):
|
||||
method_url = 'exportChatInviteLink'
|
||||
payload = {'chat_id': chat_id}
|
||||
@ -874,9 +1042,33 @@ def set_chat_title(token, chat_id, title):
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def set_my_commands(token, commands):
|
||||
def get_my_commands(token, scope=None, language_code=None):
|
||||
method_url = r'getMyCommands'
|
||||
payload = {}
|
||||
if scope:
|
||||
payload['scope'] = scope.to_json()
|
||||
if language_code:
|
||||
payload['language_code'] = language_code
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def set_my_commands(token, commands, scope=None, language_code=None):
|
||||
method_url = r'setMyCommands'
|
||||
payload = {'commands': _convert_list_json_serializable(commands)}
|
||||
if scope:
|
||||
payload['scope'] = scope.to_json()
|
||||
if language_code:
|
||||
payload['language_code'] = language_code
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def delete_my_commands(token, scope=None, language_code=None):
|
||||
method_url = r'deleteMyCommands'
|
||||
payload = {}
|
||||
if scope:
|
||||
payload['scope'] = scope.to_json()
|
||||
if language_code:
|
||||
payload['language_code'] = language_code
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -913,7 +1105,7 @@ def unpin_all_chat_messages(token, chat_id):
|
||||
# Updating messages
|
||||
|
||||
def edit_message_text(token, text, chat_id=None, message_id=None, inline_message_id=None, parse_mode=None,
|
||||
disable_web_page_preview=None, reply_markup=None):
|
||||
entities = None, disable_web_page_preview=None, reply_markup=None):
|
||||
method_url = r'editMessageText'
|
||||
payload = {'text': text}
|
||||
if chat_id:
|
||||
@ -924,6 +1116,8 @@ def edit_message_text(token, text, chat_id=None, message_id=None, inline_message
|
||||
payload['inline_message_id'] = inline_message_id
|
||||
if parse_mode:
|
||||
payload['parse_mode'] = parse_mode
|
||||
if entities:
|
||||
payload['entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(entities))
|
||||
if disable_web_page_preview is not None:
|
||||
payload['disable_web_page_preview'] = disable_web_page_preview
|
||||
if reply_markup:
|
||||
@ -932,7 +1126,7 @@ def edit_message_text(token, text, chat_id=None, message_id=None, inline_message
|
||||
|
||||
|
||||
def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_message_id=None,
|
||||
parse_mode=None, reply_markup=None):
|
||||
parse_mode=None, caption_entities=None,reply_markup=None):
|
||||
method_url = r'editMessageCaption'
|
||||
payload = {'caption': caption}
|
||||
if chat_id:
|
||||
@ -943,6 +1137,8 @@ def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_m
|
||||
payload['inline_message_id'] = inline_message_id
|
||||
if parse_mode:
|
||||
payload['parse_mode'] = parse_mode
|
||||
if caption_entities:
|
||||
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
|
||||
if reply_markup:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
@ -981,7 +1177,7 @@ def delete_message(token, chat_id, message_id, timeout=None):
|
||||
method_url = r'deleteMessage'
|
||||
payload = {'chat_id': chat_id, 'message_id': message_id}
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -989,7 +1185,8 @@ def delete_message(token, chat_id, message_id, timeout=None):
|
||||
|
||||
def send_game(
|
||||
token, chat_id, game_short_name,
|
||||
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None):
|
||||
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None,
|
||||
allow_sending_without_reply=None):
|
||||
method_url = r'sendGame'
|
||||
payload = {'chat_id': chat_id, 'game_short_name': game_short_name}
|
||||
if disable_notification is not None:
|
||||
@ -999,7 +1196,9 @@ def send_game(
|
||||
if reply_markup:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -1060,11 +1259,11 @@ def get_game_high_scores(token, user_id, chat_id=None, message_id=None, inline_m
|
||||
|
||||
def send_invoice(
|
||||
token, chat_id, title, description, invoice_payload, provider_token, currency, prices,
|
||||
start_parameter, photo_url=None, photo_size=None, photo_width=None, photo_height=None,
|
||||
start_parameter = None, 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,
|
||||
disable_notification=None, reply_to_message_id=None, reply_markup=None, provider_data=None,
|
||||
timeout=None):
|
||||
timeout=None, allow_sending_without_reply=None, max_tip_amount=None, suggested_tip_amounts=None):
|
||||
"""
|
||||
Use this method to send invoices. On success, the sent Message is returned.
|
||||
:param token: Bot's token (you don't need to fill this)
|
||||
@ -1092,12 +1291,18 @@ def send_invoice(
|
||||
: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:
|
||||
:param allow_sending_without_reply:
|
||||
:param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency
|
||||
:param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest units of the currency.
|
||||
At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount.
|
||||
:return:
|
||||
"""
|
||||
method_url = r'sendInvoice'
|
||||
payload = {'chat_id': chat_id, 'title': title, 'description': description, 'payload': invoice_payload,
|
||||
'provider_token': provider_token, 'start_parameter': start_parameter, 'currency': currency,
|
||||
'provider_token': provider_token, 'currency': currency,
|
||||
'prices': _convert_list_json_serializable(prices)}
|
||||
if start_parameter:
|
||||
payload['start_parameter'] = start_parameter
|
||||
if photo_url:
|
||||
payload['photo_url'] = photo_url
|
||||
if photo_size:
|
||||
@ -1129,7 +1334,13 @@ def send_invoice(
|
||||
if provider_data:
|
||||
payload['provider_data'] = provider_data
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if max_tip_amount is not None:
|
||||
payload['max_tip_amount'] = max_tip_amount
|
||||
if suggested_tip_amounts is not None:
|
||||
payload['suggested_tip_amounts'] = json.dumps(suggested_tip_amounts)
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -1226,15 +1437,17 @@ def upload_sticker_file(token, user_id, png_sticker):
|
||||
|
||||
|
||||
def create_new_sticker_set(
|
||||
token, user_id, name, title, png_sticker, emojis,
|
||||
token, user_id, name, title, emojis, png_sticker, tgs_sticker,
|
||||
contains_masks=None, mask_position=None):
|
||||
method_url = 'createNewStickerSet'
|
||||
payload = {'user_id': user_id, 'name': name, 'title': title, 'emojis': emojis}
|
||||
stype = 'png_sticker' if png_sticker else 'tgs_sticker'
|
||||
sticker = png_sticker or tgs_sticker
|
||||
files = None
|
||||
if not util.is_string(png_sticker):
|
||||
files = {'png_sticker': png_sticker}
|
||||
if not util.is_string(sticker):
|
||||
files = {stype: sticker}
|
||||
else:
|
||||
payload['png_sticker'] = png_sticker
|
||||
payload[stype] = sticker
|
||||
if contains_masks is not None:
|
||||
payload['contains_masks'] = contains_masks
|
||||
if mask_position:
|
||||
@ -1242,14 +1455,16 @@ def create_new_sticker_set(
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
|
||||
|
||||
def add_sticker_to_set(token, user_id, name, png_sticker, emojis, mask_position):
|
||||
def add_sticker_to_set(token, user_id, name, emojis, png_sticker, tgs_sticker, mask_position):
|
||||
method_url = 'addStickerToSet'
|
||||
payload = {'user_id': user_id, 'name': name, 'emojis': emojis}
|
||||
stype = 'png_sticker' if png_sticker else 'tgs_sticker'
|
||||
sticker = png_sticker or tgs_sticker
|
||||
files = None
|
||||
if not util.is_string(png_sticker):
|
||||
files = {'png_sticker': png_sticker}
|
||||
if not util.is_string(sticker):
|
||||
files = {stype: sticker}
|
||||
else:
|
||||
payload['png_sticker'] = png_sticker
|
||||
payload[stype] = sticker
|
||||
if mask_position:
|
||||
payload['mask_position'] = mask_position.to_json()
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
@ -1267,12 +1482,14 @@ def delete_sticker_from_set(token, sticker):
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
def send_poll(
|
||||
token, chat_id,
|
||||
question, options,
|
||||
is_anonymous = None, type = None, allows_multiple_answers = None, correct_option_id = None,
|
||||
explanation = None, explanation_parse_mode=None, open_period = None, close_date = None, is_closed = None,
|
||||
disable_notifications=False, reply_to_message_id=None, reply_markup=None, timeout=None):
|
||||
disable_notification=False, reply_to_message_id=None, allow_sending_without_reply=None,
|
||||
reply_markup=None, timeout=None, explanation_entities=None):
|
||||
method_url = r'sendPoll'
|
||||
payload = {
|
||||
'chat_id': str(chat_id),
|
||||
@ -1301,14 +1518,19 @@ def send_poll(
|
||||
if is_closed is not None:
|
||||
payload['is_closed'] = is_closed
|
||||
|
||||
if disable_notifications:
|
||||
payload['disable_notification'] = disable_notifications
|
||||
if disable_notification:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if reply_to_message_id is not None:
|
||||
payload['reply_to_message_id'] = reply_to_message_id
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if reply_markup is not None:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
if timeout:
|
||||
payload['connect-timeout'] = timeout
|
||||
payload['timeout'] = timeout
|
||||
if explanation_entities:
|
||||
payload['explanation_entities'] = json.dumps(
|
||||
types.MessageEntity.to_list_of_dicts(explanation_entities))
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -1355,7 +1577,7 @@ def _convert_poll_options(poll_options):
|
||||
elif isinstance(poll_options[0], str):
|
||||
# Compatibility mode with previous bug when only list of string was accepted as poll_options
|
||||
return poll_options
|
||||
elif isinstance(poll_options[0], types.JsonSerializable):
|
||||
elif isinstance(poll_options[0], types.PollOption):
|
||||
return [option.text for option in poll_options]
|
||||
else:
|
||||
return poll_options
|
||||
|
2416
telebot/types.py
2416
telebot/types.py
File diff suppressed because it is too large
Load Diff
206
telebot/util.py
206
telebot/util.py
@ -4,19 +4,24 @@ import re
|
||||
import string
|
||||
import threading
|
||||
import traceback
|
||||
import warnings
|
||||
import functools
|
||||
from typing import Any, Callable, List, Dict, Optional, Union
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
import queue as Queue
|
||||
import logging
|
||||
|
||||
from telebot import types
|
||||
|
||||
try:
|
||||
# noinspection PyPackageRequirements
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
pil_imported = True
|
||||
except:
|
||||
pil_imported = False
|
||||
|
||||
MAX_MESSAGE_LENGTH = 4096
|
||||
|
||||
logger = logging.getLogger('TeleBot')
|
||||
|
||||
thread_local = threading.local()
|
||||
@ -28,7 +33,15 @@ content_type_media = [
|
||||
|
||||
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'
|
||||
'supergroup_chat_created', 'channel_chat_created', 'migrate_to_chat_id', 'migrate_from_chat_id', 'pinned_message',
|
||||
'proximity_alert_triggered', 'voice_chat_scheduled', 'voice_chat_started', 'voice_chat_ended',
|
||||
'voice_chat_participants_invited', 'message_auto_delete_timer_changed'
|
||||
]
|
||||
|
||||
update_types = [
|
||||
"update_id", "message", "edited_message", "channel_post", "edited_channel_post", "inline_query",
|
||||
"chosen_inline_result", "callback_query", "shipping_query", "pre_checkout_query", "poll", "poll_answer",
|
||||
"my_chat_member", "chat_member"
|
||||
]
|
||||
|
||||
class WorkerThread(threading.Thread):
|
||||
@ -165,15 +178,19 @@ def async_dec():
|
||||
def is_string(var):
|
||||
return isinstance(var, str)
|
||||
|
||||
|
||||
def is_dict(var):
|
||||
return isinstance(var, dict)
|
||||
|
||||
|
||||
def is_bytes(var):
|
||||
return isinstance(var, bytes)
|
||||
|
||||
|
||||
def is_pil_image(var):
|
||||
return pil_imported and isinstance(var, Image.Image)
|
||||
|
||||
|
||||
def pil_image_to_file(image, extension='JPEG', quality='web_low'):
|
||||
if pil_imported:
|
||||
photoBuffer = BytesIO()
|
||||
@ -184,17 +201,18 @@ def pil_image_to_file(image, extension='JPEG', quality='web_low'):
|
||||
else:
|
||||
raise RuntimeError('PIL module is not imported')
|
||||
|
||||
def is_command(text):
|
||||
|
||||
def is_command(text: str) -> bool:
|
||||
"""
|
||||
Checks if `text` is a command. Telegram chat commands start with the '/' character.
|
||||
:param text: Text to check.
|
||||
:return: True if `text` is a command, else False.
|
||||
"""
|
||||
if (text is None): return None
|
||||
if text is None: return False
|
||||
return text.startswith('/')
|
||||
|
||||
|
||||
def extract_command(text):
|
||||
def extract_command(text: str) -> Union[str, None]:
|
||||
"""
|
||||
Extracts the command from `text` (minus the '/') if `text` is a command (see is_command).
|
||||
If `text` is not a command, this function returns None.
|
||||
@ -208,11 +226,28 @@ def extract_command(text):
|
||||
:param text: String to extract the command from
|
||||
:return: the command if `text` is a command (according to is_command), else None.
|
||||
"""
|
||||
if (text is None): return None
|
||||
if text is None: return None
|
||||
return text.split()[0].split('@')[0][1:] if is_command(text) else None
|
||||
|
||||
|
||||
def split_string(text, chars_per_string):
|
||||
def extract_arguments(text: str) -> str:
|
||||
"""
|
||||
Returns the argument after the command.
|
||||
|
||||
Examples:
|
||||
extract_arguments("/get name"): 'name'
|
||||
extract_arguments("/get"): ''
|
||||
extract_arguments("/get@botName name"): 'name'
|
||||
|
||||
:param text: String to extract the arguments from a command
|
||||
:return: the arguments if `text` is a command (according to is_command), else None.
|
||||
"""
|
||||
regexp = re.compile(r"/\w*(@\w*)*\s*([\s\S]*)", re.IGNORECASE)
|
||||
result = regexp.match(text)
|
||||
return result.group(2) if is_command(text) else None
|
||||
|
||||
|
||||
def split_string(text: str, chars_per_string: int) -> List[str]:
|
||||
"""
|
||||
Splits one string into multiple strings, with a maximum amount of `chars_per_string` characters per string.
|
||||
This is very useful for splitting one giant message into multiples.
|
||||
@ -223,6 +258,107 @@ def split_string(text, chars_per_string):
|
||||
"""
|
||||
return [text[i:i + chars_per_string] for i in range(0, len(text), chars_per_string)]
|
||||
|
||||
|
||||
def smart_split(text: str, chars_per_string: int=MAX_MESSAGE_LENGTH) -> List[str]:
|
||||
"""
|
||||
Splits one string into multiple strings, with a maximum amount of `chars_per_string` characters per string.
|
||||
This is very useful for splitting one giant message into multiples.
|
||||
If `chars_per_string` > 4096: `chars_per_string` = 4096.
|
||||
Splits by '\n', '. ' or ' ' in exactly this priority.
|
||||
|
||||
:param text: The text to split
|
||||
:param chars_per_string: The number of maximum characters per part the text is split to.
|
||||
:return: The splitted text as a list of strings.
|
||||
"""
|
||||
|
||||
def _text_before_last(substr: str) -> str:
|
||||
return substr.join(part.split(substr)[:-1]) + substr
|
||||
|
||||
if chars_per_string > MAX_MESSAGE_LENGTH: chars_per_string = MAX_MESSAGE_LENGTH
|
||||
|
||||
parts = []
|
||||
while True:
|
||||
if len(text) < chars_per_string:
|
||||
parts.append(text)
|
||||
return parts
|
||||
|
||||
part = text[:chars_per_string]
|
||||
|
||||
if "\n" in part: part = _text_before_last("\n")
|
||||
elif ". " in part: part = _text_before_last(". ")
|
||||
elif " " in part: part = _text_before_last(" ")
|
||||
|
||||
parts.append(part)
|
||||
text = text[len(part):]
|
||||
|
||||
|
||||
def escape(text: str) -> str:
|
||||
"""
|
||||
Replaces the following chars in `text` ('&' with '&', '<' with '<' and '>' with '>').
|
||||
|
||||
:param text: the text to escape
|
||||
:return: the escaped text
|
||||
"""
|
||||
chars = {"&": "&", "<": "<", ">": ">"}
|
||||
for old, new in chars.items(): text = text.replace(old, new)
|
||||
return text
|
||||
|
||||
|
||||
def user_link(user: types.User, include_id: bool=False) -> str:
|
||||
"""
|
||||
Returns an HTML user link. This is useful for reports.
|
||||
Attention: Don't forget to set parse_mode to 'HTML'!
|
||||
|
||||
Example:
|
||||
bot.send_message(your_user_id, user_link(message.from_user) + ' started the bot!', parse_mode='HTML')
|
||||
|
||||
:param user: the user (not the user_id)
|
||||
:param include_id: include the user_id
|
||||
:return: HTML user link
|
||||
"""
|
||||
name = escape(user.first_name)
|
||||
return (f"<a href='tg://user?id={user.id}'>{name}</a>"
|
||||
+ (f" (<pre>{user.id}</pre>)" if include_id else ""))
|
||||
|
||||
|
||||
def quick_markup(values: Dict[str, Dict[str, Any]], row_width: int=2) -> types.InlineKeyboardMarkup:
|
||||
"""
|
||||
Returns a reply markup from a dict in this format: {'text': kwargs}
|
||||
This is useful to avoid always typing 'btn1 = InlineKeyboardButton(...)' 'btn2 = InlineKeyboardButton(...)'
|
||||
|
||||
Example:
|
||||
quick_markup({
|
||||
'Twitter': {'url': 'https://twitter.com'},
|
||||
'Facebook': {'url': 'https://facebook.com'},
|
||||
'Back': {'callback_data': 'whatever'}
|
||||
}, row_width=2):
|
||||
returns an InlineKeyboardMarkup with two buttons in a row, one leading to Twitter, the other to facebook
|
||||
and a back button below
|
||||
|
||||
kwargs can be:
|
||||
{
|
||||
'url': None,
|
||||
'callback_data': None,
|
||||
'switch_inline_query': None,
|
||||
'switch_inline_query_current_chat': None,
|
||||
'callback_game': None,
|
||||
'pay': None,
|
||||
'login_url': None
|
||||
}
|
||||
|
||||
:param values: a dict containing all buttons to create in this format: {text: kwargs} {str:}
|
||||
:param row_width: int row width
|
||||
:return: InlineKeyboardMarkup
|
||||
"""
|
||||
markup = types.InlineKeyboardMarkup(row_width=row_width)
|
||||
buttons = [
|
||||
types.InlineKeyboardButton(text=text, **kwargs)
|
||||
for text, kwargs in values.items()
|
||||
]
|
||||
markup.add(*buttons)
|
||||
return markup
|
||||
|
||||
|
||||
# CREDITS TO http://stackoverflow.com/questions/12317940#answer-12320352
|
||||
def or_set(self):
|
||||
self._set()
|
||||
@ -243,8 +379,10 @@ def orify(e, changed_callback):
|
||||
e.set = lambda: or_set(e)
|
||||
e.clear = lambda: or_clear(e)
|
||||
|
||||
|
||||
def OrEvent(*events):
|
||||
or_event = threading.Event()
|
||||
|
||||
def changed():
|
||||
bools = [ev.is_set() for ev in events]
|
||||
if any(bools):
|
||||
@ -263,22 +401,6 @@ def OrEvent(*events):
|
||||
changed()
|
||||
return or_event
|
||||
|
||||
def extract_arguments(text):
|
||||
"""
|
||||
Returns the argument after the command.
|
||||
|
||||
Examples:
|
||||
extract_arguments("/get name"): 'name'
|
||||
extract_arguments("/get"): ''
|
||||
extract_arguments("/get@botName name"): 'name'
|
||||
|
||||
:param text: String to extract the arguments from a command
|
||||
:return: the arguments if `text` is a command (according to is_command), else None.
|
||||
"""
|
||||
regexp = re.compile(r"/\w*(@\w*)*\s*([\s\S]*)",re.IGNORECASE)
|
||||
result = regexp.match(text)
|
||||
return result.group(2) if is_command(text) else None
|
||||
|
||||
|
||||
def per_thread(key, construct_value, reset=False):
|
||||
if reset or not hasattr(thread_local, key):
|
||||
@ -287,26 +409,34 @@ def per_thread(key, construct_value, reset=False):
|
||||
|
||||
return getattr(thread_local, key)
|
||||
|
||||
|
||||
def chunks(lst, n):
|
||||
"""Yield successive n-sized chunks from lst."""
|
||||
# https://stackoverflow.com/a/312464/9935473
|
||||
for i in range(0, len(lst), n):
|
||||
yield lst[i:i + 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
|
||||
when the function is used."""
|
||||
# https://stackoverflow.com/a/30253848/441814
|
||||
@functools.wraps(func)
|
||||
def new_func(*args, **kwargs):
|
||||
warnings.simplefilter('always', DeprecationWarning) # turn off filter
|
||||
warnings.warn("Call to deprecated function {}.".format(func.__name__),
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.simplefilter('default', DeprecationWarning) # reset filter
|
||||
return func(*args, **kwargs)
|
||||
return new_func
|
||||
|
||||
def deprecated(warn: bool=False, alternative: Optional[Callable]=None):
|
||||
"""
|
||||
Use this decorator to mark functions as deprecated.
|
||||
When the function is used, an info (or warning if `warn` is True) is logged.
|
||||
:param warn: If True a warning is logged else an info
|
||||
:param alternative: The new function to use instead
|
||||
"""
|
||||
def decorator(function):
|
||||
def wrapper(*args, **kwargs):
|
||||
if not warn:
|
||||
logger.info(f"`{function.__name__}` is deprecated."
|
||||
+ (f" Use `{alternative.__name__}` instead" if alternative else ""))
|
||||
else:
|
||||
logger.warn(f"`{function.__name__}` is deprecated."
|
||||
+ (f" Use `{alternative.__name__}` instead" if alternative else ""))
|
||||
return function(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Versions should comply with PEP440.
|
||||
# This line is parsed in setup.py:
|
||||
__version__ = '3.7.8'
|
||||
__version__ = '3.8.3'
|
||||
|
@ -62,8 +62,11 @@ def update_type(message):
|
||||
pre_checkout_query = None
|
||||
poll = None
|
||||
poll_answer = None
|
||||
my_chat_member = None
|
||||
chat_member = None
|
||||
return types.Update(1001234038283, message, edited_message, channel_post, edited_channel_post, inline_query,
|
||||
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer)
|
||||
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer,
|
||||
my_chat_member, chat_member)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@ -78,9 +81,11 @@ def reply_to_message_update_type(reply_to_message):
|
||||
pre_checkout_query = None
|
||||
poll = None
|
||||
poll_answer = None
|
||||
my_chat_member = None
|
||||
chat_member = None
|
||||
return types.Update(1001234038284, reply_to_message, edited_message, channel_post, edited_channel_post,
|
||||
inline_query,
|
||||
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer)
|
||||
inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query,
|
||||
poll, poll_answer, my_chat_member, chat_member)
|
||||
|
||||
|
||||
def next_handler(message):
|
||||
|
@ -6,6 +6,7 @@ sys.path.append('../')
|
||||
import time
|
||||
import pytest
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import telebot
|
||||
from telebot import types
|
||||
@ -18,6 +19,14 @@ if not should_skip:
|
||||
CHAT_ID = os.environ['CHAT_ID']
|
||||
GROUP_ID = os.environ['GROUP_ID']
|
||||
|
||||
def _new_test():
|
||||
pass
|
||||
|
||||
@util.deprecated(alternative=_new_test)
|
||||
def _test():
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skipif(should_skip, reason="No environment variables configured")
|
||||
class TestTeleBot:
|
||||
@ -125,6 +134,16 @@ class TestTeleBot:
|
||||
ret_msg = tb.send_document(CHAT_ID, ret_msg.document.file_id)
|
||||
assert ret_msg.message_id
|
||||
|
||||
def test_send_file_with_filename(self):
|
||||
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
|
||||
tb = telebot.TeleBot(TOKEN)
|
||||
|
||||
ret_msg = tb.send_document(CHAT_ID, file_data)
|
||||
assert ret_msg.message_id
|
||||
|
||||
ret_msg = tb.send_document(CHAT_ID, file_data, visible_file_name="test.jpg")
|
||||
assert ret_msg.message_id
|
||||
|
||||
def test_send_file_dis_noti(self):
|
||||
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
|
||||
tb = telebot.TeleBot(TOKEN)
|
||||
@ -407,6 +426,23 @@ class TestTeleBot:
|
||||
cn = tb.get_chat_members_count(GROUP_ID)
|
||||
assert cn > 1
|
||||
|
||||
def test_export_chat_invite_link(self):
|
||||
tb = telebot.TeleBot(TOKEN)
|
||||
il = tb.export_chat_invite_link(GROUP_ID)
|
||||
assert isinstance(il, str)
|
||||
|
||||
def test_create_revoke_detailed_chat_invite_link(self):
|
||||
tb = telebot.TeleBot(TOKEN)
|
||||
cil = tb.create_chat_invite_link(GROUP_ID,
|
||||
(datetime.now() + timedelta(minutes=1)).timestamp(), member_limit=5)
|
||||
assert isinstance(cil.invite_link, str)
|
||||
assert cil.creator.id == tb.get_me().id
|
||||
assert isinstance(cil.expire_date, (float, int))
|
||||
assert cil.member_limit == 5
|
||||
assert not cil.is_revoked
|
||||
rcil = tb.revoke_chat_invite_link(GROUP_ID, cil.invite_link)
|
||||
assert rcil.is_revoked
|
||||
|
||||
def test_edit_markup(self):
|
||||
text = 'CI Test Message'
|
||||
tb = telebot.TeleBot(TOKEN)
|
||||
@ -440,8 +476,11 @@ class TestTeleBot:
|
||||
pre_checkout_query = None
|
||||
poll = None
|
||||
poll_answer = None
|
||||
my_chat_member = None
|
||||
chat_member = None
|
||||
return types.Update(-1001234038283, message, edited_message, channel_post, edited_channel_post, inline_query,
|
||||
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer)
|
||||
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer,
|
||||
my_chat_member, chat_member)
|
||||
|
||||
def test_is_string_unicode(self):
|
||||
s1 = u'string'
|
||||
@ -525,6 +564,24 @@ class TestTeleBot:
|
||||
ret_msg = tb.send_document(CHAT_ID, file_data, caption='_italic_', parse_mode='Markdown')
|
||||
assert ret_msg.caption_entities[0].type == 'italic'
|
||||
|
||||
def test_chat_commands(self):
|
||||
tb = telebot.TeleBot(TOKEN)
|
||||
command, description, lang = 'command_1', 'description of command 1', 'en'
|
||||
scope = telebot.types.BotCommandScopeChat(CHAT_ID)
|
||||
ret_msg = tb.set_my_commands([telebot.types.BotCommand(command, description)], scope, lang)
|
||||
assert ret_msg is True
|
||||
|
||||
ret_msg = tb.get_my_commands(scope, lang)
|
||||
assert ret_msg[0].command == command
|
||||
assert ret_msg[0].description == description
|
||||
|
||||
ret_msg = tb.delete_my_commands(scope, lang)
|
||||
assert ret_msg is True
|
||||
|
||||
ret_msg = tb.get_my_commands(scope, lang)
|
||||
assert ret_msg == []
|
||||
|
||||
|
||||
def test_typed_middleware_handler(self):
|
||||
from telebot import apihelper
|
||||
|
||||
@ -566,6 +623,9 @@ class TestTeleBot:
|
||||
tb.process_new_updates([update])
|
||||
time.sleep(1)
|
||||
assert update.message.text == 'got' * 2
|
||||
|
||||
def test_deprecated_dec(self):
|
||||
_test()
|
||||
|
||||
def test_chat_permissions(self):
|
||||
return # CHAT_ID is private chat, no permissions can be set
|
||||
|
@ -210,7 +210,7 @@ def test_json_poll_answer():
|
||||
poll_answer = types.PollAnswer.de_json(jsonstring)
|
||||
assert poll_answer.poll_id == '5895675970559410186'
|
||||
assert isinstance(poll_answer.user, types.User)
|
||||
assert poll_answer.options_ids == [1]
|
||||
assert poll_answer.option_ids == [1]
|
||||
|
||||
|
||||
def test_KeyboardButtonPollType():
|
||||
@ -219,3 +219,24 @@ def test_KeyboardButtonPollType():
|
||||
json_str = markup.to_json()
|
||||
assert 'request_poll' in json_str
|
||||
assert 'quiz' in json_str
|
||||
|
||||
|
||||
def test_json_chat_invite_link():
|
||||
json_string = r'{"invite_link": "https://t.me/joinchat/z-abCdEFghijKlMn", "creator": {"id": 329343347, "is_bot": false, "first_name": "Test", "username": "test_user", "last_name": "User", "language_code": "en"}, "is_primary": false, "is_revoked": false, "expire_date": 1624119999, "member_limit": 10}'
|
||||
invite_link = types.ChatInviteLink.de_json(json_string)
|
||||
assert invite_link.invite_link == 'https://t.me/joinchat/z-abCdEFghijKlMn'
|
||||
assert isinstance(invite_link.creator, types.User)
|
||||
assert not invite_link.is_primary
|
||||
assert not invite_link.is_revoked
|
||||
assert invite_link.expire_date == 1624119999
|
||||
assert invite_link.member_limit == 10
|
||||
|
||||
def test_chat_member_updated():
|
||||
json_string = r'{"chat": {"id": -1234567890123, "type": "supergroup", "title": "No Real Group", "username": "NoRealGroup"}, "from": {"id": 133869498, "is_bot": false, "first_name": "Vincent"}, "date": 1624119999, "old_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "member"}, "new_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "administrator"}}'
|
||||
cm_updated = types.ChatMemberUpdated.de_json(json_string)
|
||||
assert cm_updated.chat.id == -1234567890123
|
||||
assert cm_updated.from_user.id == 133869498
|
||||
assert cm_updated.date == 1624119999
|
||||
assert cm_updated.old_chat_member.status == "member"
|
||||
assert cm_updated.new_chat_member.status == "administrator"
|
||||
|
||||
|
Reference in New Issue
Block a user