mirror of
https://github.com/eternnoir/pyTelegramBotAPI.git
synced 2023-08-10 21:12:57 +03:00
Compare commits
335 Commits
4.2.1
...
3892b0fb80
Author | SHA1 | Date | |
---|---|---|---|
3892b0fb80 | |||
fbb9a73fc0 | |||
db5c53b8e5 | |||
6e8abc709e | |||
752f35614c | |||
a2893945b2 | |||
1686ce4f44 | |||
24b1129c8a | |||
7f9dac4147 | |||
f96775e9eb | |||
1342cab259 | |||
dd8125cbd0 | |||
8769802744 | |||
a7fb8a2bec | |||
d7f34ae370 | |||
7d931abe37 | |||
f52124827f | |||
0d0f9ef330 | |||
24cdcb1bcc | |||
7994a80a00 | |||
b2662274f9 | |||
a21ab203a1 | |||
e689e968db | |||
808810e6d6 | |||
5a60846c7f | |||
d2c1615392 | |||
7cdb48c3e0 | |||
28ae0867f8 | |||
e84cc0e0af | |||
42ce47914d | |||
4401780ba9 | |||
82f056e88a | |||
6b19d631d1 | |||
ee7adb00df | |||
82a8aa65f0 | |||
72aaf44dc7 | |||
1d0efce76e | |||
74d0604c05 | |||
1943f659bc | |||
d6ec104829 | |||
7edaa51995 | |||
6bb47e9a44 | |||
8da749ee05 | |||
0c59d1187e | |||
e9d1d98f03 | |||
c63b0d6a3d | |||
388306b7fe | |||
5e3fd17436 | |||
ccc09ffaf3 | |||
e086303535 | |||
d6e93f85f1 | |||
d954f8f5b3 | |||
33375ac135 | |||
28662876a2 | |||
0f44fd3c40 | |||
8fefd7b5b3 | |||
7567750276 | |||
f526a9d8a4 | |||
feffe726dd | |||
3ff7e28467 | |||
5d74e18d1a | |||
91b665ea94 | |||
6e4c63b09c | |||
42efb8488c | |||
03a567ea93 | |||
3f28bb6e9d | |||
e051dda113 | |||
856debe7d2 | |||
42955d1886 | |||
a2f3cd03e1 | |||
7e68721475 | |||
7965ba4f69 | |||
aba4d3e246 | |||
caae6bb93f | |||
096d7a4eea | |||
ab4140ba9f | |||
77c3587012 | |||
efb1b44e59 | |||
2c8793b794 | |||
146fd57b10 | |||
8a12ae3565 | |||
2d8c2312e3 | |||
f9cd0d7e08 | |||
59cd1a00e7 | |||
836130a718 | |||
a7db2d8d9c | |||
c022d49996 | |||
825827cb1e | |||
cd92b70d6b | |||
617c990994 | |||
9b959373db | |||
76c0197ab7 | |||
7d9658b062 | |||
db0c946780 | |||
c6202da36f | |||
532011138c | |||
191164cba0 | |||
5688aaa03b | |||
88a76c0a15 | |||
e1dc6d7beb | |||
730d11012d | |||
b43b636ba0 | |||
a7ca6c057e | |||
bd002c6429 | |||
453df01f26 | |||
24ae38cca6 | |||
3b386965ea | |||
5077289d0d | |||
a54b21cb50 | |||
fa80cb0002 | |||
ad5b92b650 | |||
9b1b324ab4 | |||
e444bc2a0b | |||
dd25432359 | |||
cfbbfe84ad | |||
b25d2846e9 | |||
ab64e17464 | |||
3a5db47c1b | |||
4812dcb02b | |||
b146df346d | |||
5f2713bcfb | |||
a1bf961fd2 | |||
a8451a5e30 | |||
9417c49d8e | |||
1a40f1da7a | |||
5e28f27764 | |||
43d0e10ba4 | |||
9652fdbecb | |||
110575ca40 | |||
22b4e636e2 | |||
1688a466f4 | |||
625da4cdd9 | |||
a9e0f5b7b0 | |||
7b1b1a7caa | |||
a6477541c0 | |||
b652a9f6dc | |||
73b2813512 | |||
ace28983b6 | |||
e82675320c | |||
1e88671799 | |||
1cdf9640d7 | |||
91badb53e5 | |||
477d02468d | |||
9f3a270fae | |||
244b058648 | |||
886c9b9bc0 | |||
41025ba97b | |||
4875bb6188 | |||
5f91c3d4e6 | |||
7b62915a5b | |||
05c3cb2c1d | |||
60a96d1400 | |||
dcd0df93da | |||
f15101fc6f | |||
5f03253398 | |||
8fab55e937 | |||
60a23665cb | |||
b292b275cb | |||
403028bf35 | |||
3dda5cff06 | |||
dd589e2490 | |||
c8fb83c97c | |||
854f6a9506 | |||
be0557c2b5 | |||
436422e4da | |||
fc72576aaa | |||
f69a2ba044 | |||
c45e06c694 | |||
78bdf1ca4e | |||
3c7d3c0196 | |||
441a5793cc | |||
388477686b | |||
4f654d9e12 | |||
ac12d0fc02 | |||
b8ebe4fd58 | |||
c84896391e | |||
995e28e9d8 | |||
1bfc082d46 | |||
1a35bbb127 | |||
e585c77830 | |||
5ca92ff637 | |||
f4c76553ed | |||
75baf6dd96 | |||
301b9288a4 | |||
70b9fc86d2 | |||
dde9cd323c | |||
01a6827542 | |||
b960a9e574 | |||
102fe3a8fb | |||
292df419ba | |||
7993e1d1c9 | |||
7309f92c36 | |||
7875ff293d | |||
4adac4d852 | |||
38bff65caf | |||
9ecadf1bc1 | |||
5d7ae385ec | |||
74e9780b30 | |||
9b20f41ece | |||
967309120e | |||
94be2abdbd | |||
6c31b53cd9 | |||
9bfc0b2c6f | |||
fc374ec57a | |||
7a8e60ddc2 | |||
7f43f26886 | |||
4521982837 | |||
30c43b557c | |||
10b5886dcc | |||
93b97fc3fe | |||
1f6e60fd74 | |||
5337d4838d | |||
ae5d183db0 | |||
0d85a34551 | |||
002c608d45 | |||
ec766a3e43 | |||
0ef8d04ed2 | |||
3a86916e72 | |||
b41435f407 | |||
f689d90815 | |||
966f2e7ef7 | |||
9075430210 | |||
68095ad69a | |||
8c3d1e608c | |||
6822f18cbb | |||
6e4f2e19d6 | |||
8bbd062d13 | |||
5f7ccc8c9b | |||
5b1483f646 | |||
3cd86d0e93 | |||
a893fbc358 | |||
6fd2a38fe9 | |||
b89ecb3e5a | |||
2e5590b566 | |||
733bb2ebbb | |||
64a22457e2 | |||
0c8e94d2c6 | |||
b9436821e0 | |||
a8af9120de | |||
0655a1f6b6 | |||
97dbedaa54 | |||
4028b44d07 | |||
661218c7e3 | |||
cd4a9add68 | |||
7d2915c7f9 | |||
ce56a035b5 | |||
9fa79aabc0 | |||
62fad9ca3a | |||
388f055643 | |||
71be20636a | |||
3b38d1b46e | |||
1e0c2ea633 | |||
4e7652be7a | |||
723075d2da | |||
7ba021871a | |||
d7cb819502 | |||
5ee2aa77c6 | |||
80cf5d8d5b | |||
69277400b7 | |||
8d380b4913 | |||
23d20e0753 | |||
6fc7beba57 | |||
8d49d22074 | |||
6aa97d055f | |||
e55938e23a | |||
4166fb229e | |||
2e9947277a | |||
c350ea0ced | |||
588b5c4d89 | |||
91d0877c61 | |||
8045ad56ea | |||
124b07ee44 | |||
195974ddc1 | |||
2b081b42bb | |||
321d241483 | |||
ad4ff5835e | |||
a3cda2e0ff | |||
cf2eb1fec7 | |||
7eb759d1fd | |||
a07bf86c30 | |||
64c4aca3b7 | |||
40465643b9 | |||
56fbf491bc | |||
685c071056 | |||
fdbc0e6a61 | |||
7fe8d27686 | |||
9050f4af1f | |||
9140044956 | |||
2e6b6bda53 | |||
8d8aa5a380 | |||
ae2dbd00fa | |||
6550a5d745 | |||
593b27358b | |||
a51ff0f100 | |||
a96bc802bc | |||
df8f34e726 | |||
00998ac9c8 | |||
3f243c64ca | |||
034241ba31 | |||
ed6fb57cb5 | |||
b71507387f | |||
e7d0ec1f6c | |||
b3b318fd28 | |||
d334f5cb8d | |||
7f06424980 | |||
7490aa0d26 | |||
e59e2ee2ee | |||
f25dcad10c | |||
24a9491ec0 | |||
744549defe | |||
c86fc4c3fa | |||
943396767c | |||
13fffe58a1 | |||
7fe60e19ef | |||
ba9bf17f46 | |||
373ee4b45b | |||
e5f0ba67fc | |||
096d58ae71 | |||
ed5b47cb96 | |||
e92946301f | |||
7588c9fb9f | |||
6d10bfefbd | |||
cebfbb83fa | |||
5a06d8021b | |||
a75841aa8e | |||
e76649bb49 | |||
7567c6cd71 | |||
751deeafd7 | |||
3ebefa15bf | |||
bb19687854 | |||
311eec6888 | |||
08fc32b70a | |||
555257a3fe | |||
5a03ab62d0 | |||
038be81db3 |
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
## Description
|
||||
Include changes, new features and etc:
|
||||
|
||||
## Describe your tests
|
||||
How did you test your change?
|
||||
|
||||
Python version:
|
||||
|
||||
OS:
|
||||
|
||||
## Checklist:
|
||||
- [ ] I added/edited example on new feature/change (if exists)
|
||||
- [ ] My changes won't break backward compatibility
|
||||
- [ ] I made changes both for sync and async
|
2
.github/workflows/setup_python.yml
vendored
2
.github/workflows/setup_python.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
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
|
||||
python-version: [ '3.6','3.7','3.8','3.9', '3.10', 'pypy-3.7', 'pypy-3.8', 'pypy-3.9']
|
||||
name: ${{ matrix.python-version }} and tests
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -64,3 +64,7 @@ testMain.py
|
||||
#VS Code
|
||||
.vscode/
|
||||
.DS_Store
|
||||
*.code-workspace
|
||||
|
||||
# documentation
|
||||
_build/
|
||||
|
19
.readthedocs.yml
Normal file
19
.readthedocs.yml
Normal file
@ -0,0 +1,19 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF and ePub
|
||||
formats: all
|
||||
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
version: 3.7
|
||||
install:
|
||||
- requirements: doc_req.txt
|
@ -1,6 +1,5 @@
|
||||
language: python
|
||||
python:
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
|
106
README.md
106
README.md
@ -1,16 +1,20 @@
|
||||
|
||||
[](https://pypi.python.org/pypi/pyTelegramBotAPI)
|
||||
[](https://pypi.python.org/pypi/pyTelegramBotAPI)
|
||||
[](https://pytba.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://travis-ci.org/eternnoir/pyTelegramBotAPI)
|
||||
[](https://pypi.org/project/pyTelegramBotAPI/)
|
||||
[](https://pypi.python.org/pypi/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>
|
||||
<p align="center">Supports both sync and async ways.</p>
|
||||
<p align="center">Both synchronous and asynchronous.</p>
|
||||
|
||||
## <p align="center">Supporting Bot API version: <a href="https://core.telegram.org/bots/api#november-5-2021">5.4</a>!
|
||||
## <p align="center">Supported Bot API version: <a href="https://core.telegram.org/bots/api#june-20-2022">6.1</a>!
|
||||
|
||||
<h2><a href='https://pytba.readthedocs.io/en/latest/index.html'>Official documentation</a></h2>
|
||||
|
||||
## Contents
|
||||
|
||||
* [Getting started](#getting-started)
|
||||
@ -58,15 +62,17 @@
|
||||
* [How can I distinguish a User and a GroupChat in message.chat?](#how-can-i-distinguish-a-user-and-a-groupchat-in-messagechat)
|
||||
* [How can I handle reocurring ConnectionResetErrors?](#how-can-i-handle-reocurring-connectionreseterrors)
|
||||
* [The Telegram Chat Group](#the-telegram-chat-group)
|
||||
* [Telegram Channel](#telegram-channel)
|
||||
* [More examples](#more-examples)
|
||||
* [Bots using this API](#bots-using-this-api)
|
||||
* [Code Template](#code-template)
|
||||
* [Bots using this library](#bots-using-this-library)
|
||||
|
||||
## Getting started
|
||||
|
||||
This API is tested with Python 3.6-3.10 and Pypy 3.
|
||||
There are two ways to install the library:
|
||||
|
||||
* Installation using pip (a Python package manager)*:
|
||||
* Installation using pip (a Python package manager):
|
||||
|
||||
```
|
||||
$ pip install pyTelegramBotAPI
|
||||
@ -78,10 +84,17 @@ $ git clone https://github.com/eternnoir/pyTelegramBotAPI.git
|
||||
$ cd pyTelegramBotAPI
|
||||
$ python setup.py install
|
||||
```
|
||||
or:
|
||||
```
|
||||
$ pip install git+https://github.com/eternnoir/pyTelegramBotAPI.git
|
||||
```
|
||||
|
||||
It is generally recommended to use the first option.
|
||||
|
||||
**While the API is production-ready, it is still under development and it has regular updates, do not forget to update it regularly by calling `pip install pytelegrambotapi --upgrade`*
|
||||
*While the API is production-ready, it is still under development and it has regular updates, do not forget to update it regularly by calling*
|
||||
```
|
||||
pip install pytelegrambotapi --upgrade
|
||||
```
|
||||
|
||||
## Writing your first bot
|
||||
|
||||
@ -343,7 +356,27 @@ def start(message):
|
||||
assert message.another_text == message.text + ':changed'
|
||||
```
|
||||
There are other examples using middleware handler in the [examples/middleware](examples/middleware) directory.
|
||||
|
||||
|
||||
#### Class-based middlewares
|
||||
There are class-based middlewares.
|
||||
Basic class-based middleware looks like this:
|
||||
```python
|
||||
class Middleware(BaseMiddleware):
|
||||
def __init__(self):
|
||||
self.update_types = ['message']
|
||||
def pre_process(self, message, data):
|
||||
data['foo'] = 'Hello' # just for example
|
||||
# we edited the data. now, this data is passed to handler.
|
||||
# return SkipHandler() -> this will skip handler
|
||||
# return CancelUpdate() -> this will cancel update
|
||||
def post_process(self, message, data, exception=None):
|
||||
print(data['foo'])
|
||||
if exception: # check for exception
|
||||
print(exception)
|
||||
```
|
||||
Class-based middleware should have to functions: post and pre process.
|
||||
So, as you can see, class-based middlewares work before and after handler execution.
|
||||
For more, check out in [examples](https://github.com/eternnoir/pyTelegramBotAPI/tree/master/examples/middleware/class_based)
|
||||
|
||||
#### Custom filters
|
||||
Also, you can use built-in custom filters. Or, you can create your own filter.
|
||||
@ -641,6 +674,8 @@ telebot.logger.setLevel(logging.DEBUG) # Outputs debug messages to console.
|
||||
```
|
||||
|
||||
### Proxy
|
||||
For sync:
|
||||
|
||||
You can use proxy for request. `apihelper.proxy` object will use by call `requests` proxies argument.
|
||||
|
||||
```python
|
||||
@ -655,6 +690,14 @@ If you want to use socket5 proxy you need install dependency `pip install reques
|
||||
apihelper.proxy = {'https':'socks5://userproxy:password@proxy_address:port'}
|
||||
```
|
||||
|
||||
For async:
|
||||
```python
|
||||
from telebot import asyncio_helper
|
||||
|
||||
asyncio_helper.proxy = 'http://127.0.0.1:3128' #url
|
||||
```
|
||||
|
||||
|
||||
### Testing
|
||||
You can disable or change the interaction with real Telegram server by using
|
||||
```python
|
||||
@ -684,9 +727,13 @@ Result will be:
|
||||
|
||||
|
||||
## API conformance
|
||||
|
||||
* ✔ [Bot API 6.1](https://core.telegram.org/bots/api#june-20-2022)
|
||||
* ✔ [Bot API 6.0](https://core.telegram.org/bots/api#april-16-2022)
|
||||
* ✔ [Bot API 5.7](https://core.telegram.org/bots/api#january-31-2022)
|
||||
* ✔ [Bot API 5.6](https://core.telegram.org/bots/api#december-30-2021)
|
||||
* ✔ [Bot API 5.5](https://core.telegram.org/bots/api#december-7-2021)
|
||||
* ✔ [Bot API 5.4](https://core.telegram.org/bots/api#november-5-2021)
|
||||
* ➕ [Bot API 5.3](https://core.telegram.org/bots/api#june-25-2021) - ChatMemberXXX classes are full copies of ChatMember
|
||||
* ➕ [Bot API 5.3](https://core.telegram.org/bots/api#june-25-2021) - ChatMember* 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)
|
||||
@ -700,18 +747,6 @@ Result will be:
|
||||
* ✔ [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)
|
||||
* ✔ [Bot API 3.1](https://core.telegram.org/bots/api-changelog#june-30-2017)
|
||||
* ✔ [Bot API 3.0](https://core.telegram.org/bots/api-changelog#may-18-2017)
|
||||
* ✔ [Bot API 2.3.1](https://core.telegram.org/bots/api-changelog#december-4-2016)
|
||||
* ✔ [Bot API 2.3](https://core.telegram.org/bots/api-changelog#november-21-2016)
|
||||
* ✔ [Bot API 2.2](https://core.telegram.org/bots/api-changelog#october-3-2016)
|
||||
* ✔ [Bot API 2.1](https://core.telegram.org/bots/api-changelog#may-22-2016)
|
||||
* ✔ [Bot API 2.0](https://core.telegram.org/bots/api-changelog#april-9-2016)
|
||||
|
||||
|
||||
## AsyncTeleBot
|
||||
@ -727,6 +762,7 @@ Echo Bot example on AsyncTeleBot:
|
||||
# It echoes any incoming text messages.
|
||||
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
import asyncio
|
||||
bot = AsyncTeleBot('TOKEN')
|
||||
|
||||
|
||||
@ -746,7 +782,7 @@ async def echo_message(message):
|
||||
await bot.reply_to(message, message.text)
|
||||
|
||||
|
||||
bot.polling()
|
||||
asyncio.run(bot.polling())
|
||||
```
|
||||
As you can see here, keywords are await and async.
|
||||
|
||||
@ -754,10 +790,10 @@ As you can see here, keywords are await and async.
|
||||
Asynchronous tasks depend on processor performance. Many asynchronous tasks can run parallelly, while thread tasks will block each other.
|
||||
|
||||
### Differences in AsyncTeleBot
|
||||
AsyncTeleBot has different middlewares. See example on [middlewares](https://github.com/coder2020official/pyTelegramBotAPI/tree/master/examples/asynchronous_telebot/middleware)
|
||||
AsyncTeleBot is asynchronous. It uses aiohttp instead of requests module.
|
||||
|
||||
### Examples
|
||||
See more examples in our [examples](https://github.com/coder2020official/pyTelegramBotAPI/tree/master/examples/asynchronous_telebot) folder
|
||||
See more examples in our [examples](https://github.com/eternnoir/pyTelegramBotAPI/tree/master/examples/asynchronous_telebot) folder
|
||||
|
||||
|
||||
## F.A.Q.
|
||||
@ -766,7 +802,6 @@ See more examples in our [examples](https://github.com/coder2020official/pyTeleg
|
||||
Telegram Bot API support new type Chat for message.chat.
|
||||
|
||||
- Check the ```type``` attribute in ```Chat``` object:
|
||||
-
|
||||
```python
|
||||
if message.chat.type == "private":
|
||||
# private chat message
|
||||
@ -791,14 +826,26 @@ Bot instances that were idle for a long time might be rejected by the server whe
|
||||
Get help. Discuss. Chat.
|
||||
|
||||
* Join the [pyTelegramBotAPI Telegram Chat Group](https://telegram.me/joinchat/Bn4ixj84FIZVkwhk2jag6A)
|
||||
|
||||
## Telegram Channel
|
||||
|
||||
Join the [News channel](https://t.me/pyTelegramBotAPI). Here we will post releases and updates.
|
||||
|
||||
## More examples
|
||||
|
||||
* [Echo Bot](https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/echo_bot.py)
|
||||
* [Deep Linking](https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/deep_linking.py)
|
||||
* [next_step_handler Example](https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/step_example.py)
|
||||
|
||||
## Bots using this API
|
||||
## Code Template
|
||||
Template is a ready folder that contains architecture of basic project.
|
||||
Here are some examples of template:
|
||||
|
||||
* [AsyncTeleBot template](https://github.com/coder2020official/asynctelebot_template)
|
||||
* [TeleBot template](https://github.com/coder2020official/telebot_template)
|
||||
|
||||
|
||||
## Bots using this library
|
||||
* [SiteAlert bot](https://telegram.me/SiteAlert_bot) ([source](https://github.com/ilteoood/SiteAlert-Python)) by *ilteoood* - Monitors websites and sends a notification on changes
|
||||
* [TelegramLoggingBot](https://github.com/aRandomStranger/TelegramLoggingBot) by *aRandomStranger*
|
||||
* [Telegram LMGTFY_bot](https://github.com/GabrielRF/telegram-lmgtfy_bot) by *GabrielRF* - Let me Google that for you.
|
||||
@ -845,5 +892,12 @@ Get help. Discuss. Chat.
|
||||
* [ETHGasFeeTrackerBot](https://t.me/ETHGasFeeTrackerBot) ([Source](https://github.com/DevAdvik/ETHGasFeeTrackerBot]) by *DevAdvik* - Get Live Ethereum Gas Fees in GWEI
|
||||
* [Google Sheet Bot](https://github.com/JoachimStanislaus/Tele_Sheet_bot) by [JoachimStanislaus](https://github.com/JoachimStanislaus). This bot can help you to track your expenses by uploading your bot entries to your google sheet.
|
||||
* [GrandQuiz Bot](https://github.com/Carlosma7/TFM-GrandQuiz) by [Carlosma7](https://github.com/Carlosma7). This bot is a trivia game that allows you to play with people from different ages. This project addresses the use of a system through chatbots to carry out a social and intergenerational game as an alternative to traditional game development.
|
||||
|
||||
* [Diccionario de la RAE](https://t.me/dleraebot) ([source](https://github.com/studentenherz/dleraebot)) This bot lets you find difinitions of words in Spanish using [RAE's dictionary](https://dle.rae.es/). It features direct message and inline search.
|
||||
* [remoteTelegramShell](https://github.com/EnriqueMoran/remoteTelegramShell) by [EnriqueMoran](https://github.com/EnriqueMoran). Control your LinuxOS computer through Telegram.
|
||||
* [Pyfram-telegram-bot](https://github.com/skelly37/pyfram-telegram-bot) Query wolframalpha.com and make use of its API through Telegram.
|
||||
* [TranslateThisVideoBot](https://gitlab.com/WuerfelDev/translatethisvideo) This Bot can understand spoken text in videos and translate it to English
|
||||
* [Zyprexa](https://t.me/mathemathicsBot) ([source](https://github.com/atif5/zyprexa)) Zyprexa can solve, help you solve any mathematical problem you encounter and convert your regular mathematical expressions into beautiful imagery using LaTeX.
|
||||
* [Bincode-telegram-bot](https://github.com/tusharhero/bincode-telegram-bot) by [tusharhero](https://github.com/tusharhero) - Makes [bincodes](https://github.com/tusharhero/bincode) from text provides and also converts them back to text.
|
||||
* [hydrolib_bot](https://github.com/Mayson90/hydrolib_bot) Toolset for Hydrophilia tabletop game (game cards, rules, structure...).
|
||||
|
||||
**Want to have your bot listed here? Just make a pull request. Only bots with public source code are accepted.**
|
||||
|
5
doc_req.txt
Normal file
5
doc_req.txt
Normal file
@ -0,0 +1,5 @@
|
||||
-r requirements.txt
|
||||
|
||||
furo
|
||||
sphinx_copybutton
|
||||
git+https://github.com/eternnoir/pyTelegramBotAPI.git
|
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
BIN
docs/source/_static/logo.png
Normal file
BIN
docs/source/_static/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
BIN
docs/source/_static/logo2.png
Normal file
BIN
docs/source/_static/logo2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
49
docs/source/async_version/index.rst
Normal file
49
docs/source/async_version/index.rst
Normal file
@ -0,0 +1,49 @@
|
||||
====================
|
||||
AsyncTeleBot
|
||||
====================
|
||||
|
||||
|
||||
.. meta::
|
||||
:description: Asynchronous pyTelegramBotAPI
|
||||
:keywords: ptba, pytba, pyTelegramBotAPI, asynctelebot, documentation
|
||||
|
||||
|
||||
AsyncTeleBot methods
|
||||
--------------------
|
||||
|
||||
.. automodule:: telebot.async_telebot
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
|
||||
Asyncio filters
|
||||
---------------
|
||||
|
||||
.. automodule:: telebot.asyncio_filters
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Asynchronous storage for states
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: telebot.asyncio_storage
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Asyncio handler backends
|
||||
------------------------
|
||||
|
||||
|
||||
|
||||
.. automodule:: telebot.asyncio_handler_backends
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
|
17
docs/source/calldata.rst
Normal file
17
docs/source/calldata.rst
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
=====================
|
||||
Callback data factory
|
||||
=====================
|
||||
|
||||
.. meta::
|
||||
:description: Callback data factory in pyTelegramBotAPI
|
||||
:keywords: ptba, pytba, pyTelegramBotAPI, callbackdatafactory, guide, callbackdata, factory
|
||||
|
||||
|
||||
callback\_data file
|
||||
-----------------------------
|
||||
|
||||
.. automodule:: telebot.callback_data
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
70
docs/source/conf.py
Normal file
70
docs/source/conf.py
Normal file
@ -0,0 +1,70 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'pyTelegramBotAPI Documentation'
|
||||
copyright = '2022, coder2020official'
|
||||
author = 'coder2020official'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '4.6.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autosectionlabel',
|
||||
'sphinx.ext.autodoc',
|
||||
"sphinx.ext.autosummary",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx_copybutton",
|
||||
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = []
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'furo'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
#html_logo = 'logo.png'
|
||||
html_theme_options = {
|
||||
"light_css_variables": {
|
||||
"color-brand-primary": "#7C4DFF",
|
||||
"color-brand-content": "#7C4DFF",
|
||||
},
|
||||
"light_logo": "logo.png",
|
||||
"dark_logo": "logo2.png",
|
||||
}
|
12
docs/source/formatting.rst
Normal file
12
docs/source/formatting.rst
Normal file
@ -0,0 +1,12 @@
|
||||
==================
|
||||
Formatting options
|
||||
==================
|
||||
|
||||
.. meta::
|
||||
:description: Formatting options in pyTelegramBotAPI
|
||||
:keywords: html, markdown, parse_mode, formatting, ptba, pytba, pyTelegramBotAPI
|
||||
|
||||
.. automodule:: telebot.formatting
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
69
docs/source/index.rst
Normal file
69
docs/source/index.rst
Normal file
@ -0,0 +1,69 @@
|
||||
.. pyTelegramBotAPI documentation master file, created by
|
||||
sphinx-quickstart on Fri Feb 18 20:58:37 2022.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
|
||||
Welcome to pyTelegramBotAPI's documentation!
|
||||
============================================
|
||||
|
||||
.. meta::
|
||||
:description: Official documentation of pyTelegramBotAPI
|
||||
:keywords: ptba, pytba, pyTelegramBotAPI, documentation, guide
|
||||
|
||||
|
||||
=======
|
||||
TeleBot
|
||||
=======
|
||||
TeleBot is synchronous and asynchronous implementation of `Telegram Bot API <https://core.telegram.org/bots/api>`_.
|
||||
|
||||
Chats
|
||||
-----
|
||||
English chat: `Private chat <https://telegram.me/joinchat/Bn4ixj84FIZVkwhk2jag6A>`__
|
||||
|
||||
Russian chat: `@pytelegrambotapi_talks_ru <https://t.me/pytelegrambotapi_talks_ru>`__
|
||||
|
||||
News: `@pyTelegramBotAPI <https://t.me/pytelegrambotapi>`__
|
||||
|
||||
Pypi: `Pypi <https://pypi.org/project/pyTelegramBotAPI/>`__
|
||||
|
||||
Source: `Github repository <https://github.com/eternnoir/pyTelegramBotAPI>`__
|
||||
|
||||
Some features:
|
||||
--------------
|
||||
Easy to learn and use.
|
||||
|
||||
Easy to understand.
|
||||
|
||||
Both sync and async.
|
||||
|
||||
Examples on features.
|
||||
|
||||
States
|
||||
|
||||
And more...
|
||||
|
||||
Content
|
||||
--------
|
||||
.. toctree::
|
||||
|
||||
install
|
||||
quick_start
|
||||
types
|
||||
sync_version/index
|
||||
async_version/index
|
||||
calldata
|
||||
util
|
||||
formatting
|
||||
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
45
docs/source/install.rst
Normal file
45
docs/source/install.rst
Normal file
@ -0,0 +1,45 @@
|
||||
==================
|
||||
Installation Guide
|
||||
==================
|
||||
|
||||
.. meta::
|
||||
:description: Installation of pyTelegramBotAPI
|
||||
:keywords: ptba, pytba, pyTelegramBotAPI, installation, guide
|
||||
|
||||
|
||||
Using PIP
|
||||
----------
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install pyTelegramBotAPI
|
||||
|
||||
Using pipenv
|
||||
------------
|
||||
.. code-block:: bash
|
||||
|
||||
$ pipenv install pyTelegramBotAPI
|
||||
|
||||
By cloning repository
|
||||
---------------------
|
||||
.. code-block:: bash
|
||||
|
||||
$ git clone https://github.com/eternnoir/pyTelegramBotAPI.git
|
||||
$ cd pyTelegramBotAPI
|
||||
$ python setup.py install
|
||||
|
||||
Directly using pip
|
||||
------------------
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install git+https://github.com/eternnoir/pyTelegramBotAPI.git
|
||||
|
||||
|
||||
It is generally recommended to use the first option.
|
||||
|
||||
While the API is production-ready, it is still under development and it has regular updates, do not forget to update it regularly by calling:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install pytelegrambotapi --upgrade
|
||||
|
||||
|
20
docs/source/quick_start.rst
Normal file
20
docs/source/quick_start.rst
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
===========
|
||||
Quick start
|
||||
===========
|
||||
|
||||
.. meta::
|
||||
:description: Quickstart guide
|
||||
:keywords: ptba, pytba, pyTelegramBotAPI, quickstart, guide
|
||||
|
||||
Synchronous TeleBot
|
||||
-------------------
|
||||
.. literalinclude:: ../../examples/echo_bot.py
|
||||
:language: python
|
||||
|
||||
Asynchronous TeleBot
|
||||
--------------------
|
||||
.. literalinclude:: ../../examples/asynchronous_telebot/echo_bot.py
|
||||
:language: python
|
||||
|
||||
|
39
docs/source/sync_version/index.rst
Normal file
39
docs/source/sync_version/index.rst
Normal file
@ -0,0 +1,39 @@
|
||||
===============
|
||||
TeleBot version
|
||||
===============
|
||||
|
||||
.. meta::
|
||||
:description: Synchronous pyTelegramBotAPI documentation
|
||||
:keywords: ptba, pytba, pyTelegramBotAPI, methods, guide, files, sync
|
||||
|
||||
TeleBot methods
|
||||
---------------
|
||||
.. automodule:: telebot
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
custom_filters file
|
||||
------------------------------
|
||||
|
||||
.. automodule:: telebot.custom_filters
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Synchronous storage for states
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: telebot.storage
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
handler_backends file
|
||||
--------------------------------
|
||||
|
||||
.. automodule:: telebot.handler_backends
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
10
docs/source/types.rst
Normal file
10
docs/source/types.rst
Normal file
@ -0,0 +1,10 @@
|
||||
============
|
||||
Types of API
|
||||
============
|
||||
|
||||
|
||||
|
||||
.. automodule:: telebot.types
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
16
docs/source/util.rst
Normal file
16
docs/source/util.rst
Normal file
@ -0,0 +1,16 @@
|
||||
============
|
||||
Utils
|
||||
============
|
||||
|
||||
.. meta::
|
||||
:description: Utils in pyTelegramBotAPI
|
||||
:keywords: ptba, pytba, pyTelegramBotAPI, utils, guide
|
||||
|
||||
|
||||
util file
|
||||
-------------------
|
||||
|
||||
.. automodule:: telebot.util
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
@ -9,7 +9,7 @@ import telebot
|
||||
from telebot import types
|
||||
|
||||
# Initialize bot with your token
|
||||
bot = telebot.TeleBot(TOKEN)
|
||||
bot = telebot.TeleBot('TOKEN')
|
||||
|
||||
# The `users` variable is needed to contain chat ids that are either in the search or in the active dialog, like {chat_id, chat_id}
|
||||
users = {}
|
||||
@ -47,7 +47,7 @@ def find(message: types.Message):
|
||||
if message.chat.id not in users:
|
||||
bot.send_message(message.chat.id, 'Finding...')
|
||||
|
||||
if freeid == None:
|
||||
if freeid is None:
|
||||
freeid = message.chat.id
|
||||
else:
|
||||
# Question:
|
||||
|
@ -0,0 +1,26 @@
|
||||
from telebot import types
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot.asyncio_filters import AdvancedCustomFilter
|
||||
from telebot.callback_data import CallbackData, CallbackDataFilter
|
||||
|
||||
calendar_factory = CallbackData("year", "month", prefix="calendar")
|
||||
calendar_zoom = CallbackData("year", prefix="calendar_zoom")
|
||||
|
||||
|
||||
class CalendarCallbackFilter(AdvancedCustomFilter):
|
||||
key = 'calendar_config'
|
||||
|
||||
async def check(self, call: types.CallbackQuery, config: CallbackDataFilter):
|
||||
return config.check(query=call)
|
||||
|
||||
|
||||
class CalendarZoomCallbackFilter(AdvancedCustomFilter):
|
||||
key = 'calendar_zoom_config'
|
||||
|
||||
async def check(self, call: types.CallbackQuery, config: CallbackDataFilter):
|
||||
return config.check(query=call)
|
||||
|
||||
|
||||
def bind_filters(bot: AsyncTeleBot):
|
||||
bot.add_custom_filter(CalendarCallbackFilter())
|
||||
bot.add_custom_filter(CalendarZoomCallbackFilter())
|
@ -0,0 +1,92 @@
|
||||
import calendar
|
||||
from datetime import date, timedelta
|
||||
|
||||
from filters import calendar_factory, calendar_zoom
|
||||
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
EMTPY_FIELD = '1'
|
||||
WEEK_DAYS = [calendar.day_abbr[i] for i in range(7)]
|
||||
MONTHS = [(i, calendar.month_name[i]) for i in range(1, 13)]
|
||||
|
||||
|
||||
def generate_calendar_days(year: int, month: int):
|
||||
keyboard = InlineKeyboardMarkup(row_width=7)
|
||||
today = date.today()
|
||||
|
||||
keyboard.add(
|
||||
InlineKeyboardButton(
|
||||
text=date(year=year, month=month, day=1).strftime('%b %Y'),
|
||||
callback_data=EMTPY_FIELD
|
||||
)
|
||||
)
|
||||
keyboard.add(*[
|
||||
InlineKeyboardButton(
|
||||
text=day,
|
||||
callback_data=EMTPY_FIELD
|
||||
)
|
||||
for day in WEEK_DAYS
|
||||
])
|
||||
|
||||
for week in calendar.Calendar().monthdayscalendar(year=year, month=month):
|
||||
week_buttons = []
|
||||
for day in week:
|
||||
day_name = ' '
|
||||
if day == today.day and today.year == year and today.month == month:
|
||||
day_name = '🔘'
|
||||
elif day != 0:
|
||||
day_name = str(day)
|
||||
week_buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text=day_name,
|
||||
callback_data=EMTPY_FIELD
|
||||
)
|
||||
)
|
||||
keyboard.add(*week_buttons)
|
||||
|
||||
previous_date = date(year=year, month=month, day=1) - timedelta(days=1)
|
||||
next_date = date(year=year, month=month, day=1) + timedelta(days=31)
|
||||
|
||||
keyboard.add(
|
||||
InlineKeyboardButton(
|
||||
text='Previous month',
|
||||
callback_data=calendar_factory.new(year=previous_date.year, month=previous_date.month)
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text='Zoom out',
|
||||
callback_data=calendar_zoom.new(year=year)
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text='Next month',
|
||||
callback_data=calendar_factory.new(year=next_date.year, month=next_date.month)
|
||||
),
|
||||
)
|
||||
|
||||
return keyboard
|
||||
|
||||
|
||||
def generate_calendar_months(year: int):
|
||||
keyboard = InlineKeyboardMarkup(row_width=3)
|
||||
keyboard.add(
|
||||
InlineKeyboardButton(
|
||||
text=date(year=year, month=1, day=1).strftime('Year %Y'),
|
||||
callback_data=EMTPY_FIELD
|
||||
)
|
||||
)
|
||||
keyboard.add(*[
|
||||
InlineKeyboardButton(
|
||||
text=month,
|
||||
callback_data=calendar_factory.new(year=year, month=month_number)
|
||||
)
|
||||
for month_number, month in MONTHS
|
||||
])
|
||||
keyboard.add(
|
||||
InlineKeyboardButton(
|
||||
text='Previous year',
|
||||
callback_data=calendar_zoom.new(year=year - 1)
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text='Next year',
|
||||
callback_data=calendar_zoom.new(year=year + 1)
|
||||
)
|
||||
)
|
||||
return keyboard
|
@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This Example will show you an advanced usage of CallbackData.
|
||||
In this example calendar was implemented
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import date
|
||||
|
||||
from filters import calendar_factory, calendar_zoom, bind_filters
|
||||
from keyboards import generate_calendar_days, generate_calendar_months, EMTPY_FIELD
|
||||
from telebot import types
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
|
||||
API_TOKEN = ''
|
||||
bot = AsyncTeleBot(API_TOKEN)
|
||||
|
||||
|
||||
@bot.message_handler(commands='start')
|
||||
async def start_command_handler(message: types.Message):
|
||||
await bot.send_message(message.chat.id,
|
||||
f"Hello {message.from_user.first_name}. This bot is an example of calendar keyboard."
|
||||
"\nPress /calendar to see it.")
|
||||
|
||||
|
||||
@bot.message_handler(commands='calendar')
|
||||
async def calendar_command_handler(message: types.Message):
|
||||
now = date.today()
|
||||
await bot.send_message(message.chat.id, 'Calendar',
|
||||
reply_markup=generate_calendar_days(year=now.year, month=now.month))
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, calendar_config=calendar_factory.filter())
|
||||
async def calendar_action_handler(call: types.CallbackQuery):
|
||||
callback_data: dict = calendar_factory.parse(callback_data=call.data)
|
||||
year, month = int(callback_data['year']), int(callback_data['month'])
|
||||
|
||||
await bot.edit_message_reply_markup(call.message.chat.id, call.message.id,
|
||||
reply_markup=generate_calendar_days(year=year, month=month))
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, calendar_zoom_config=calendar_zoom.filter())
|
||||
async def calendar_zoom_out_handler(call: types.CallbackQuery):
|
||||
callback_data: dict = calendar_zoom.parse(callback_data=call.data)
|
||||
year = int(callback_data.get('year'))
|
||||
|
||||
await bot.edit_message_reply_markup(call.message.chat.id, call.message.id,
|
||||
reply_markup=generate_calendar_months(year=year))
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=lambda call: call.data == EMTPY_FIELD)
|
||||
async def callback_empty_field_handler(call: types.CallbackQuery):
|
||||
await bot.answer_callback_query(call.id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bind_filters(bot)
|
||||
asyncio.run(bot.infinity_polling())
|
@ -84,4 +84,5 @@ async def back_callback(call: types.CallbackQuery):
|
||||
|
||||
|
||||
bot.add_custom_filter(ProductsCallbackFilter())
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
@ -8,4 +8,5 @@ async def make_some(message: telebot.types.ChatJoinRequest):
|
||||
await bot.send_message(message.chat.id, 'I accepted a new user!')
|
||||
await bot.approve_chat_join_request(message.chat.id, message.from_user.id)
|
||||
|
||||
bot.polling(skip_pending=True)
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
@ -23,11 +23,12 @@ async def my_chat_m(message: types.ChatMemberUpdated):
|
||||
#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'
|
||||
#'proximity_alert_triggered', 'video_chat_scheduled', 'video_chat_started', 'video_chat_ended',
|
||||
#'video_chat_participants_invited', 'message_auto_delete_timer_changed'
|
||||
# this handler deletes service messages
|
||||
|
||||
@bot.message_handler(content_types=util.content_type_service)
|
||||
async def delall(message: types.Message):
|
||||
await bot.delete_message(message.chat.id,message.message_id)
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling(allowed_updates=util.update_types))
|
||||
|
@ -9,4 +9,6 @@ async def answer_for_admin(message):
|
||||
|
||||
# Register filter
|
||||
bot.add_custom_filter(asyncio_filters.IsAdminFilter(bot))
|
||||
bot.polling()
|
||||
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This Example will show you usage of TextFilter
|
||||
In this example you will see how to use TextFilter
|
||||
with (message_handler, callback_query_handler, poll_handler)
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from telebot import types
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot.asyncio_filters import TextMatchFilter, TextFilter, IsReplyFilter
|
||||
|
||||
bot = AsyncTeleBot("")
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(equals='hello'))
|
||||
async def hello_handler(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(equals='hello', ignore_case=True))
|
||||
async def hello_handler_ignore_case(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text + ' ignore case')
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(contains=['good', 'bad']))
|
||||
async def contains_handler(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(contains=['good', 'bad'], ignore_case=True))
|
||||
async def contains_handler_ignore_case(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text + ' ignore case')
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(starts_with='st')) # stArk, steve, stONE
|
||||
async def starts_with_handler(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(starts_with='st', ignore_case=True)) # STark, sTeve, stONE
|
||||
async def starts_with_handler_ignore_case(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text + ' ignore case')
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(ends_with='ay')) # wednesday, SUNday, WeekDay
|
||||
async def ends_with_handler(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(ends_with='ay', ignore_case=True)) # wednesdAY, sundAy, WeekdaY
|
||||
async def ends_with_handler_ignore_case(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text + ' ignore case')
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(equals='/callback'))
|
||||
async def send_callback(message: types.Message):
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
keyboard=[
|
||||
[types.InlineKeyboardButton(text='callback data', callback_data='example')],
|
||||
[types.InlineKeyboardButton(text='ignore case callback data', callback_data='ExAmPLe')]
|
||||
]
|
||||
)
|
||||
await bot.send_message(message.chat.id, message.text, reply_markup=keyboard)
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=TextFilter(equals='example'))
|
||||
async def callback_query_handler(call: types.CallbackQuery):
|
||||
await bot.answer_callback_query(call.id, call.data, show_alert=True)
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=TextFilter(equals='example', ignore_case=True))
|
||||
async def callback_query_handler_ignore_case(call: types.CallbackQuery):
|
||||
await bot.answer_callback_query(call.id, call.data + " ignore case", show_alert=True)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(equals='/poll'))
|
||||
async def send_poll(message: types.Message):
|
||||
await bot.send_poll(message.chat.id, question='When do you prefer to work?', options=['Morning', 'Night'])
|
||||
await bot.send_poll(message.chat.id, question='WHEN DO you pRefeR to worK?', options=['Morning', 'Night'])
|
||||
|
||||
|
||||
@bot.poll_handler(func=None, text=TextFilter(equals='When do you prefer to work?'))
|
||||
async def poll_question_handler(poll: types.Poll):
|
||||
print(poll.question)
|
||||
|
||||
|
||||
@bot.poll_handler(func=None, text=TextFilter(equals='When do you prefer to work?', ignore_case=True))
|
||||
async def poll_question_handler_ignore_case(poll: types.Poll):
|
||||
print(poll.question + ' ignore case')
|
||||
|
||||
|
||||
# either hi or contains one of (привет, salom)
|
||||
@bot.message_handler(text=TextFilter(equals="hi", contains=('привет', 'salom'), ignore_case=True))
|
||||
async def multiple_patterns_handler(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
# starts with one of (mi, mea) for ex. minor, milk, meal, meat
|
||||
@bot.message_handler(text=TextFilter(starts_with=['mi', 'mea'], ignore_case=True))
|
||||
async def multiple_starts_with_handler(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
# ends with one of (es, on) for ex. Jones, Davies, Johnson, Wilson
|
||||
@bot.message_handler(text=TextFilter(ends_with=['es', 'on'], ignore_case=True))
|
||||
async def multiple_ends_with_handler(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
# !ban /ban .ban !бан /бан .бан
|
||||
@bot.message_handler(is_reply=True,
|
||||
text=TextFilter(starts_with=('!', '/', '.'), ends_with=['ban', 'бан'], ignore_case=True))
|
||||
async def ban_command_handler(message: types.Message):
|
||||
if len(message.text) == 4 and message.chat.type != 'private':
|
||||
try:
|
||||
await bot.ban_chat_member(message.chat.id, message.reply_to_message.from_user.id)
|
||||
await bot.reply_to(message.reply_to_message, 'Banned.')
|
||||
except Exception as err:
|
||||
print(err.args)
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bot.add_custom_filter(TextMatchFilter())
|
||||
bot.add_custom_filter(IsReplyFilter())
|
||||
asyncio.run(bot.polling())
|
@ -40,4 +40,5 @@ async def bye_user(message):
|
||||
bot.add_custom_filter(MainFilter())
|
||||
bot.add_custom_filter(IsAdmin())
|
||||
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -14,4 +14,5 @@ async def not_admin(message):
|
||||
|
||||
# Do not forget to register
|
||||
bot.add_custom_filter(telebot.asyncio_filters.ChatFilter())
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -19,4 +19,5 @@ async def text_filter(message):
|
||||
bot.add_custom_filter(telebot.asyncio_filters.IsReplyFilter())
|
||||
bot.add_custom_filter(telebot.asyncio_filters.ForwardFilter())
|
||||
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -17,4 +17,5 @@ async def text_filter(message):
|
||||
bot.add_custom_filter(telebot.asyncio_filters.TextMatchFilter())
|
||||
bot.add_custom_filter(telebot.asyncio_filters.TextStartsFilter())
|
||||
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -1,15 +1,27 @@
|
||||
import telebot
|
||||
from telebot import asyncio_filters
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
bot = AsyncTeleBot('TOKEN')
|
||||
|
||||
# list of storages, you can use any storage
|
||||
from telebot.asyncio_storage import StateMemoryStorage
|
||||
|
||||
# new feature for states.
|
||||
from telebot.asyncio_handler_backends import State, StatesGroup
|
||||
|
||||
# default state storage is statememorystorage
|
||||
bot = AsyncTeleBot('TOKEN', state_storage=StateMemoryStorage())
|
||||
|
||||
|
||||
# Just create different statesgroup
|
||||
class MyStates(StatesGroup):
|
||||
name = State() # statesgroup should contain states
|
||||
surname = State()
|
||||
age = State()
|
||||
|
||||
|
||||
|
||||
class MyStates:
|
||||
name = 1
|
||||
surname = 2
|
||||
age = 3
|
||||
|
||||
# set_state -> sets a new state
|
||||
# delete_state -> delets state if exists
|
||||
# get_state -> returns state if exists
|
||||
|
||||
|
||||
@bot.message_handler(commands=['start'])
|
||||
@ -17,7 +29,7 @@ async def start_ex(message):
|
||||
"""
|
||||
Start command. Here we are starting state
|
||||
"""
|
||||
await bot.set_state(message.from_user.id, MyStates.name)
|
||||
await bot.set_state(message.from_user.id, MyStates.name, message.chat.id)
|
||||
await bot.send_message(message.chat.id, 'Hi, write me a name')
|
||||
|
||||
|
||||
@ -28,39 +40,45 @@ async def any_state(message):
|
||||
Cancel state
|
||||
"""
|
||||
await bot.send_message(message.chat.id, "Your state was cancelled.")
|
||||
await bot.delete_state(message.from_user.id)
|
||||
await bot.delete_state(message.from_user.id, message.chat.id)
|
||||
|
||||
@bot.message_handler(state=MyStates.name)
|
||||
async def name_get(message):
|
||||
"""
|
||||
State 1. Will process when user's state is 1.
|
||||
State 1. Will process when user's state is MyStates.name.
|
||||
"""
|
||||
await bot.send_message(message.chat.id, f'Now write me a surname')
|
||||
await bot.set_state(message.from_user.id, MyStates.surname)
|
||||
async with bot.retrieve_data(message.from_user.id) as data:
|
||||
await bot.set_state(message.from_user.id, MyStates.surname, message.chat.id)
|
||||
async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
|
||||
data['name'] = message.text
|
||||
|
||||
|
||||
@bot.message_handler(state=MyStates.surname)
|
||||
async def ask_age(message):
|
||||
"""
|
||||
State 2. Will process when user's state is 2.
|
||||
State 2. Will process when user's state is MyStates.surname.
|
||||
"""
|
||||
await bot.send_message(message.chat.id, "What is your age?")
|
||||
await bot.set_state(message.from_user.id, MyStates.age)
|
||||
async with bot.retrieve_data(message.from_user.id) as data:
|
||||
await bot.set_state(message.from_user.id, MyStates.age, message.chat.id)
|
||||
async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
|
||||
data['surname'] = message.text
|
||||
|
||||
# result
|
||||
@bot.message_handler(state=MyStates.age, is_digit=True)
|
||||
async def ready_for_answer(message):
|
||||
async with bot.retrieve_data(message.from_user.id) as data:
|
||||
"""
|
||||
State 3. Will process when user's state is MyStates.age.
|
||||
"""
|
||||
async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
|
||||
await bot.send_message(message.chat.id, "Ready, take a look:\n<b>Name: {name}\nSurname: {surname}\nAge: {age}</b>".format(name=data['name'], surname=data['surname'], age=message.text), parse_mode="html")
|
||||
await bot.delete_state(message.from_user.id)
|
||||
await bot.delete_state(message.from_user.id, message.chat.id)
|
||||
|
||||
#incorrect number
|
||||
@bot.message_handler(state=MyStates.age, is_digit=False)
|
||||
async def age_incorrect(message):
|
||||
"""
|
||||
Will process for wrong input when state is MyState.age
|
||||
"""
|
||||
await bot.send_message(message.chat.id, 'Looks like you are submitting a string in the field age. Please enter a number')
|
||||
|
||||
# register filters
|
||||
@ -68,7 +86,6 @@ async def age_incorrect(message):
|
||||
bot.add_custom_filter(asyncio_filters.StateFilter(bot))
|
||||
bot.add_custom_filter(asyncio_filters.IsDigitFilter())
|
||||
|
||||
# set saving states into file.
|
||||
bot.enable_saving_states() # you can delete this if you do not need to save states
|
||||
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
@ -17,4 +17,5 @@ async def new_message(message: telebot.types.Message):
|
||||
await bot.edit_message_text(chat_id=message.chat.id, message_id=result_message.id, text='<i>Done!</i>', parse_mode='HTML')
|
||||
|
||||
|
||||
bot.polling(skip_pending=True)
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -23,4 +23,5 @@ async def echo_message(message):
|
||||
await bot.reply_to(message, message.text)
|
||||
|
||||
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -24,4 +24,5 @@ async def photo_send(message: telebot.types.Message):
|
||||
|
||||
|
||||
|
||||
bot.polling(skip_pending=True)
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
54
examples/asynchronous_telebot/formatting_example.py
Normal file
54
examples/asynchronous_telebot/formatting_example.py
Normal file
@ -0,0 +1,54 @@
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot import formatting
|
||||
|
||||
bot = AsyncTeleBot('token')
|
||||
|
||||
|
||||
@bot.message_handler(commands=['start'])
|
||||
async def start_message(message):
|
||||
await bot.send_message(
|
||||
message.chat.id,
|
||||
# function which connects all strings
|
||||
formatting.format_text(
|
||||
formatting.mbold(message.from_user.first_name),
|
||||
formatting.mitalic(message.from_user.first_name),
|
||||
formatting.munderline(message.from_user.first_name),
|
||||
formatting.mstrikethrough(message.from_user.first_name),
|
||||
formatting.mcode(message.from_user.first_name),
|
||||
separator=" " # separator separates all strings
|
||||
),
|
||||
parse_mode='MarkdownV2'
|
||||
)
|
||||
|
||||
# just a bold text using markdownv2
|
||||
await bot.send_message(
|
||||
message.chat.id,
|
||||
formatting.mbold(message.from_user.first_name),
|
||||
parse_mode='MarkdownV2'
|
||||
)
|
||||
|
||||
# html
|
||||
await bot.send_message(
|
||||
message.chat.id,
|
||||
formatting.format_text(
|
||||
formatting.hbold(message.from_user.first_name),
|
||||
formatting.hitalic(message.from_user.first_name),
|
||||
formatting.hunderline(message.from_user.first_name),
|
||||
formatting.hstrikethrough(message.from_user.first_name),
|
||||
formatting.hcode(message.from_user.first_name),
|
||||
# hide_link is only for html
|
||||
formatting.hide_link('https://telegra.ph/file/c158e3a6e2a26a160b253.jpg'),
|
||||
separator=" "
|
||||
),
|
||||
parse_mode='HTML'
|
||||
)
|
||||
|
||||
# just a bold text in html
|
||||
await bot.send_message(
|
||||
message.chat.id,
|
||||
formatting.hbold(message.from_user.first_name),
|
||||
parse_mode='HTML'
|
||||
)
|
||||
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
@ -1,9 +1,7 @@
|
||||
# Just a little example of middleware handlers
|
||||
|
||||
import telebot
|
||||
from telebot.asyncio_handler_backends import BaseMiddleware
|
||||
from telebot.asyncio_handler_backends import BaseMiddleware, CancelUpdate
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot.async_telebot import CancelUpdate
|
||||
bot = AsyncTeleBot('TOKEN')
|
||||
|
||||
|
||||
@ -36,4 +34,5 @@ bot.setup_middleware(SimpleMiddleware(2))
|
||||
async def start(message):
|
||||
await bot.send_message(message.chat.id, 'Hello!')
|
||||
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -45,4 +45,5 @@ async def start(message, data: dict):
|
||||
await bot.send_message(message.chat.id, data['response'])
|
||||
|
||||
|
||||
bot.polling()
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -0,0 +1,120 @@
|
||||
import contextvars
|
||||
import gettext
|
||||
import os
|
||||
|
||||
from telebot.asyncio_handler_backends import BaseMiddleware
|
||||
|
||||
try:
|
||||
from babel.support import LazyProxy
|
||||
|
||||
babel_imported = True
|
||||
except ImportError:
|
||||
babel_imported = False
|
||||
|
||||
|
||||
class I18N(BaseMiddleware):
|
||||
"""
|
||||
This middleware provides high-level tool for internationalization
|
||||
It is based on gettext util.
|
||||
"""
|
||||
|
||||
context_lang = contextvars.ContextVar('language', default=None)
|
||||
|
||||
def __init__(self, translations_path, domain_name: str):
|
||||
super().__init__()
|
||||
self.update_types = self.process_update_types()
|
||||
|
||||
self.path = translations_path
|
||||
self.domain = domain_name
|
||||
self.translations = self.find_translations()
|
||||
|
||||
@property
|
||||
def available_translations(self):
|
||||
return list(self.translations)
|
||||
|
||||
def gettext(self, text: str, lang: str = None):
|
||||
"""
|
||||
Singular translations
|
||||
"""
|
||||
|
||||
if lang is None:
|
||||
lang = self.context_lang.get()
|
||||
|
||||
if lang not in self.translations:
|
||||
return text
|
||||
|
||||
translator = self.translations[lang]
|
||||
return translator.gettext(text)
|
||||
|
||||
def ngettext(self, singular: str, plural: str, lang: str = None, n=1):
|
||||
"""
|
||||
Plural translations
|
||||
"""
|
||||
if lang is None:
|
||||
lang = self.context_lang.get()
|
||||
|
||||
if lang not in self.translations:
|
||||
if n == 1:
|
||||
return singular
|
||||
return plural
|
||||
|
||||
translator = self.translations[lang]
|
||||
return translator.ngettext(singular, plural, n)
|
||||
|
||||
def lazy_gettext(self, text: str, lang: str = None):
|
||||
if not babel_imported:
|
||||
raise RuntimeError('babel module is not imported. Check that you installed it.')
|
||||
return LazyProxy(self.gettext, text, lang, enable_cache=False)
|
||||
|
||||
def lazy_ngettext(self, singular: str, plural: str, lang: str = None, n=1):
|
||||
if not babel_imported:
|
||||
raise RuntimeError('babel module is not imported. Check that you installed it.')
|
||||
return LazyProxy(self.ngettext, singular, plural, lang, n, enable_cache=False)
|
||||
|
||||
async def get_user_language(self, obj):
|
||||
"""
|
||||
You need to override this method and return user language
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def process_update_types(self) -> list:
|
||||
"""
|
||||
You need to override this method and return any update types which you want to be processed
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def pre_process(self, message, data):
|
||||
"""
|
||||
context language variable will be set each time when update from 'process_update_types' comes
|
||||
value is the result of 'get_user_language' method
|
||||
"""
|
||||
self.context_lang.set(await self.get_user_language(obj=message))
|
||||
|
||||
async def post_process(self, message, data, exception):
|
||||
pass
|
||||
|
||||
def find_translations(self):
|
||||
"""
|
||||
Looks for translations with passed 'domain' in passed 'path'
|
||||
"""
|
||||
if not os.path.exists(self.path):
|
||||
raise RuntimeError(f"Translations directory by path: {self.path!r} was not found")
|
||||
|
||||
result = {}
|
||||
|
||||
for name in os.listdir(self.path):
|
||||
translations_path = os.path.join(self.path, name, 'LC_MESSAGES')
|
||||
|
||||
if not os.path.isdir(translations_path):
|
||||
continue
|
||||
|
||||
po_file = os.path.join(translations_path, self.domain + '.po')
|
||||
mo_file = po_file[:-2] + 'mo'
|
||||
|
||||
if os.path.isfile(po_file) and not os.path.isfile(mo_file):
|
||||
raise FileNotFoundError(f"Translations for: {name!r} were not compiled!")
|
||||
|
||||
with open(mo_file, 'rb') as file:
|
||||
result[name] = gettext.GNUTranslations(file)
|
||||
|
||||
return result
|
@ -0,0 +1,34 @@
|
||||
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardMarkup, KeyboardButton
|
||||
|
||||
|
||||
def languages_keyboard():
|
||||
return InlineKeyboardMarkup(
|
||||
keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(text="English", callback_data='en'),
|
||||
InlineKeyboardButton(text="Русский", callback_data='ru'),
|
||||
InlineKeyboardButton(text="O'zbekcha", callback_data='uz_Latn')
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def clicker_keyboard(_):
|
||||
return InlineKeyboardMarkup(
|
||||
keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(text=_("click"), callback_data='click'),
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def menu_keyboard(_):
|
||||
keyboard = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
keyboard.add(
|
||||
KeyboardButton(text=_("My user id")),
|
||||
KeyboardButton(text=_("My user name")),
|
||||
KeyboardButton(text=_("My first name"))
|
||||
)
|
||||
|
||||
return keyboard
|
@ -0,0 +1,81 @@
|
||||
# English translations for PROJECT.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2022-02-19 18:37+0500\n"
|
||||
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
"Language-Team: en <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: keyboards.py:20
|
||||
msgid "click"
|
||||
msgstr ""
|
||||
|
||||
#: keyboards.py:29
|
||||
msgid "My user id"
|
||||
msgstr ""
|
||||
|
||||
#: keyboards.py:30
|
||||
msgid "My user name"
|
||||
msgstr ""
|
||||
|
||||
#: keyboards.py:31
|
||||
msgid "My first name"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:97
|
||||
msgid ""
|
||||
"Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n"
|
||||
"\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example\n"
|
||||
"/menu - text menu example"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:121
|
||||
msgid "Language has been changed"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:130 main.py:150
|
||||
#, fuzzy
|
||||
msgid "You have {number} click"
|
||||
msgid_plural "You have {number} clicks"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: main.py:135 main.py:155
|
||||
msgid ""
|
||||
"This is clicker.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:163
|
||||
msgid "This is ReplyKeyboardMarkup menu example in multilanguage bot."
|
||||
msgstr ""
|
||||
|
||||
#: main.py:203
|
||||
msgid "Seems you confused language"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Hello, {user_fist_name}!\n"
|
||||
#~ "This is the example of multilanguage bot.\n"
|
||||
#~ "Available commands:\n"
|
||||
#~ "\n"
|
||||
#~ "/lang - change your language\n"
|
||||
#~ "/plural - pluralization example"
|
||||
#~ msgstr ""
|
||||
|
@ -0,0 +1,82 @@
|
||||
# Russian translations for PROJECT.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2022-02-19 18:37+0500\n"
|
||||
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: ru\n"
|
||||
"Language-Team: ru <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: keyboards.py:20
|
||||
msgid "click"
|
||||
msgstr "Клик"
|
||||
|
||||
#: keyboards.py:29
|
||||
msgid "My user id"
|
||||
msgstr "Мой user id"
|
||||
|
||||
#: keyboards.py:30
|
||||
msgid "My user name"
|
||||
msgstr "Мой user name"
|
||||
|
||||
#: keyboards.py:31
|
||||
msgid "My first name"
|
||||
msgstr "Мой first name"
|
||||
|
||||
#: main.py:97
|
||||
msgid ""
|
||||
"Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n"
|
||||
"\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example\n"
|
||||
"/menu - text menu example"
|
||||
msgstr ""
|
||||
"Привет, {user_fist_name}!\n"
|
||||
"Это пример мультиязычного бота.\n"
|
||||
"Доступные команды:\n"
|
||||
"\n"
|
||||
"/lang - изменить язык\n"
|
||||
"/plural - пример плюрализации\n"
|
||||
"/menu - Пример текстового меню"
|
||||
|
||||
#: main.py:121
|
||||
msgid "Language has been changed"
|
||||
msgstr "Язык был сменён"
|
||||
|
||||
#: main.py:130 main.py:150
|
||||
msgid "You have {number} click"
|
||||
msgid_plural "You have {number} clicks"
|
||||
msgstr[0] "У вас {number} клик"
|
||||
msgstr[1] "У вас {number} клика"
|
||||
msgstr[2] "У вас {number} кликов"
|
||||
|
||||
#: main.py:135 main.py:155
|
||||
msgid ""
|
||||
"This is clicker.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"Это кликер.\n"
|
||||
"\n"
|
||||
|
||||
#: main.py:163
|
||||
msgid "This is ReplyKeyboardMarkup menu example in multilanguage bot."
|
||||
msgstr "Это пример ReplyKeyboardMarkup меню в мультиязычном боте."
|
||||
|
||||
#: main.py:203
|
||||
msgid "Seems you confused language"
|
||||
msgstr "Кажется, вы перепутали язык"
|
||||
|
@ -0,0 +1,80 @@
|
||||
# Uzbek (Latin) translations for PROJECT.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2022-02-19 18:37+0500\n"
|
||||
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: uz_Latn\n"
|
||||
"Language-Team: uz_Latn <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: keyboards.py:20
|
||||
msgid "click"
|
||||
msgstr "clik"
|
||||
|
||||
#: keyboards.py:29
|
||||
msgid "My user id"
|
||||
msgstr "Mani user id"
|
||||
|
||||
#: keyboards.py:30
|
||||
msgid "My user name"
|
||||
msgstr "Mani user name"
|
||||
|
||||
#: keyboards.py:31
|
||||
msgid "My first name"
|
||||
msgstr "Mani first name"
|
||||
|
||||
#: main.py:97
|
||||
msgid ""
|
||||
"Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n"
|
||||
"\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example\n"
|
||||
"/menu - text menu example"
|
||||
msgstr ""
|
||||
"Salom, {user_fist_name}!\n"
|
||||
"Bu multilanguage bot misoli.\n"
|
||||
"Mavjud buyruqlar:\n"
|
||||
"\n"
|
||||
"/lang - tilni ozgartirish\n"
|
||||
"/plural - pluralizatsiya misoli\n"
|
||||
"/menu - text menu misoli"
|
||||
|
||||
#: main.py:121
|
||||
msgid "Language has been changed"
|
||||
msgstr "Til ozgartirildi"
|
||||
|
||||
#: main.py:130 main.py:150
|
||||
msgid "You have {number} click"
|
||||
msgid_plural "You have {number} clicks"
|
||||
msgstr[0] "Sizda {number}ta clik"
|
||||
msgstr[1] "Sizda {number}ta clik"
|
||||
|
||||
#: main.py:135 main.py:155
|
||||
msgid ""
|
||||
"This is clicker.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"Bu clicker.\n"
|
||||
"\n"
|
||||
|
||||
#: main.py:163
|
||||
msgid "This is ReplyKeyboardMarkup menu example in multilanguage bot."
|
||||
msgstr "Bu multilanguage bot da replykeyboardmarkup menyu misoli."
|
||||
|
||||
#: main.py:203
|
||||
msgid "Seems you confused language"
|
||||
msgstr "Tilni adashtirdiz"
|
||||
|
@ -0,0 +1,214 @@
|
||||
"""
|
||||
In this example you will learn how to adapt your bot to different languages
|
||||
Using built-in middleware I18N.
|
||||
|
||||
You need to install babel package 'https://pypi.org/project/Babel/'
|
||||
Babel provides a command-line interface for working with message catalogs
|
||||
After installing babel package you have a script called 'pybabel'
|
||||
Too see all the commands open terminal and type 'pybabel --help'
|
||||
Full description for pybabel commands can be found here: 'https://babel.pocoo.org/en/latest/cmdline.html'
|
||||
|
||||
Create a directory 'locales' where our translations will be stored
|
||||
|
||||
First we need to extract texts:
|
||||
pybabel extract -o locales/{domain_name}.pot --input-dirs .
|
||||
{domain_name}.pot - is the file where all translations are saved
|
||||
The name of this file should be the same as domain which you pass to I18N class
|
||||
In this example domain_name will be 'messages'
|
||||
|
||||
For gettext (singular texts) we use '_' alias and it works perfect
|
||||
You may also you some alias for ngettext (plural texts) but you can face with a problem that
|
||||
your plural texts are not being extracted
|
||||
That is because by default 'pybabel extract' recognizes the following keywords:
|
||||
_, gettext, ngettext, ugettext, ungettext, dgettext, dngettext, N_
|
||||
To add your own keyword you can use '-k' flag
|
||||
In this example for 'ngettext' i will assign double underscore alias '__'
|
||||
|
||||
Full command with pluralization support will look so:
|
||||
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||
|
||||
Then create directories with translations (get list of all locales: 'pybabel --list-locales'):
|
||||
pybabel init -i locales/{domain_name}.pot -d locales -l en
|
||||
pybabel init -i locales/{domain_name}.pot -d locales -l ru
|
||||
pybabel init -i locales/{domain_name}.pot -d locales -l uz_Latn
|
||||
|
||||
Now you can translate the texts located in locales/{language}/LC_MESSAGES/{domain_name}.po
|
||||
After you translated all the texts you need to compile .po files:
|
||||
pybabel compile -d locales
|
||||
|
||||
When you delete/update your texts you also need to update them in .po files:
|
||||
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||
pybabel update -i locales/{domain_name}.pot -d locales
|
||||
- translate
|
||||
pybabel compile -d locales
|
||||
|
||||
If you have any exceptions check:
|
||||
- you have installed babel
|
||||
- translations are ready, so you just compiled it
|
||||
- in the commands above you replaced {domain_name} to messages
|
||||
- you are writing commands from correct path in terminal
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Union
|
||||
|
||||
import keyboards
|
||||
from telebot import types
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot.asyncio_filters import TextMatchFilter, TextFilter
|
||||
from i18n_base_middleware import I18N
|
||||
from telebot.asyncio_storage.memory_storage import StateMemoryStorage
|
||||
|
||||
|
||||
class I18NMiddleware(I18N):
|
||||
|
||||
def process_update_types(self) -> list:
|
||||
"""
|
||||
Here you need to return a list of update types which you want to be processed
|
||||
"""
|
||||
return ['message', 'callback_query']
|
||||
|
||||
async def get_user_language(self, obj: Union[types.Message, types.CallbackQuery]):
|
||||
"""
|
||||
This method is called when new update comes (only updates which you return in 'process_update_types' method)
|
||||
Returned language will be used in 'pre_process' method of parent class
|
||||
Returned language will be set to context language variable.
|
||||
If you need to get translation with user's actual language you don't have to pass it manually
|
||||
It will be automatically passed from context language value.
|
||||
However if you need some other language you can always pass it.
|
||||
"""
|
||||
|
||||
user_id = obj.from_user.id
|
||||
|
||||
if user_id not in users_lang:
|
||||
users_lang[user_id] = 'en'
|
||||
|
||||
return users_lang[user_id]
|
||||
|
||||
|
||||
storage = StateMemoryStorage()
|
||||
bot = AsyncTeleBot("", state_storage=storage)
|
||||
|
||||
i18n = I18NMiddleware(translations_path='locales', domain_name='messages')
|
||||
_ = i18n.gettext # for singular translations
|
||||
__ = i18n.ngettext # for plural translations
|
||||
|
||||
# These are example storages, do not use it in a production development
|
||||
users_lang = {}
|
||||
users_clicks = {}
|
||||
|
||||
|
||||
@bot.message_handler(commands='start')
|
||||
async def start_handler(message: types.Message):
|
||||
text = _("Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example\n"
|
||||
"/menu - text menu example")
|
||||
|
||||
# remember don't use f string for interpolation, use .format method instead
|
||||
text = text.format(user_fist_name=message.from_user.first_name)
|
||||
await bot.send_message(message.from_user.id, text)
|
||||
|
||||
|
||||
@bot.message_handler(commands='lang')
|
||||
async def change_language_handler(message: types.Message):
|
||||
await bot.send_message(message.chat.id, "Choose language\nВыберите язык\nTilni tanlang",
|
||||
reply_markup=keyboards.languages_keyboard())
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=TextFilter(contains=['en', 'ru', 'uz_Latn']))
|
||||
async def language_handler(call: types.CallbackQuery):
|
||||
lang = call.data
|
||||
users_lang[call.from_user.id] = lang
|
||||
|
||||
# When you changed user language, you have to pass it manually beacause it is not changed in context
|
||||
await bot.edit_message_text(_("Language has been changed", lang=lang), call.from_user.id, call.message.id)
|
||||
|
||||
|
||||
@bot.message_handler(commands='plural')
|
||||
async def pluralization_handler(message: types.Message):
|
||||
if not users_clicks.get(message.from_user.id):
|
||||
users_clicks[message.from_user.id] = 0
|
||||
clicks = users_clicks[message.from_user.id]
|
||||
|
||||
text = __(
|
||||
singular="You have {number} click",
|
||||
plural="You have {number} clicks",
|
||||
n=clicks
|
||||
)
|
||||
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||
|
||||
await bot.send_message(message.chat.id, text, reply_markup=keyboards.clicker_keyboard(_))
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=TextFilter(equals='click'))
|
||||
async def click_handler(call: types.CallbackQuery):
|
||||
if not users_clicks.get(call.from_user.id):
|
||||
users_clicks[call.from_user.id] = 1
|
||||
else:
|
||||
users_clicks[call.from_user.id] += 1
|
||||
|
||||
clicks = users_clicks[call.from_user.id]
|
||||
|
||||
text = __(
|
||||
singular="You have {number} click",
|
||||
plural="You have {number} clicks",
|
||||
n=clicks
|
||||
)
|
||||
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||
|
||||
await bot.edit_message_text(text, call.from_user.id, call.message.message_id,
|
||||
reply_markup=keyboards.clicker_keyboard(_))
|
||||
|
||||
|
||||
@bot.message_handler(commands='menu')
|
||||
async def menu_handler(message: types.Message):
|
||||
text = _("This is ReplyKeyboardMarkup menu example in multilanguage bot.")
|
||||
await bot.send_message(message.chat.id, text, reply_markup=keyboards.menu_keyboard(_))
|
||||
|
||||
|
||||
# For lazy tranlations
|
||||
# lazy gettext is used when you don't know user's locale
|
||||
# It can be used for example to handle text buttons in multilanguage bot
|
||||
# The actual translation will be delayed until update comes and context language is set
|
||||
l_ = i18n.lazy_gettext
|
||||
|
||||
|
||||
# Handlers below will handle text according to user's language
|
||||
@bot.message_handler(text=l_("My user id"))
|
||||
async def return_user_id(message: types.Message):
|
||||
await bot.send_message(message.chat.id, str(message.from_user.id))
|
||||
|
||||
|
||||
@bot.message_handler(text=l_("My user name"))
|
||||
async def return_user_id(message: types.Message):
|
||||
username = message.from_user.username
|
||||
if not username:
|
||||
username = '-'
|
||||
await bot.send_message(message.chat.id, username)
|
||||
|
||||
|
||||
# You can make it case insensitive
|
||||
@bot.message_handler(text=TextFilter(equals=l_("My first name"), ignore_case=True))
|
||||
async def return_user_id(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.from_user.first_name)
|
||||
|
||||
|
||||
all_menu_texts = []
|
||||
for language in i18n.available_translations:
|
||||
for menu_text in ("My user id", "My user name", "My first name"):
|
||||
all_menu_texts.append(_(menu_text, language))
|
||||
|
||||
|
||||
# When user confused language. (handles all menu buttons texts)
|
||||
@bot.message_handler(text=TextFilter(contains=all_menu_texts, ignore_case=True))
|
||||
async def missed_message(message: types.Message):
|
||||
await bot.send_message(message.chat.id, _("Seems you confused language"), reply_markup=keyboards.menu_keyboard(_))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bot.setup_middleware(i18n)
|
||||
bot.add_custom_filter(TextMatchFilter())
|
||||
asyncio.run(bot.infinity_polling())
|
17
examples/asynchronous_telebot/multibot/README.MD
Normal file
17
examples/asynchronous_telebot/multibot/README.MD
Normal file
@ -0,0 +1,17 @@
|
||||
You probably have seen bots which allow you to send them token of your bot and then handle updates providing some functionality for your bot.
|
||||
<br>
|
||||
This type of bots are called <b>multibots</b>. They are created using webhooks.
|
||||
<br>
|
||||
|
||||
This is the example of simple multibot.<br>
|
||||
In order to reproduce this example you need to have <b>domain and ssl connection</b>.
|
||||
<br>
|
||||
If you have, go to config.py and specify your data.
|
||||
<br>
|
||||
There is also file called <b>nginx_conf.conf</b>, we will use nginx as proxy-server and this file is example nginx.conf file.
|
||||
<br>
|
||||
Make sure that server_name and port are the same in both config and nginx_conf
|
||||
<br>
|
||||
(nginx_conf.conf IS NOT complete, you would probably use tools like certbot to add ssl connection to it)
|
||||
<br>
|
||||
Also, in this example I used dictionary as tokens storage, but in production you should use database so that you can re-set webhooks in case bot restarts.
|
6
examples/asynchronous_telebot/multibot/config.py
Normal file
6
examples/asynchronous_telebot/multibot/config.py
Normal file
@ -0,0 +1,6 @@
|
||||
MAIN_BOT_TOKEN = "your_main_bot_token"
|
||||
|
||||
WEBHOOK_HOST = "your_domain.com"
|
||||
WEBHOOK_PATH = "telegram_webhook"
|
||||
WEBAPP_HOST = "0.0.0.0"
|
||||
WEBAPP_PORT = 3500
|
15
examples/asynchronous_telebot/multibot/handlers.py
Normal file
15
examples/asynchronous_telebot/multibot/handlers.py
Normal file
@ -0,0 +1,15 @@
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot import types
|
||||
|
||||
|
||||
async def hello_handler(message: types.Message, bot: AsyncTeleBot):
|
||||
await bot.send_message(message.chat.id, "Hi :)")
|
||||
|
||||
|
||||
async def echo_handler(message: types.Message, bot: AsyncTeleBot):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
def register_handlers(bot: AsyncTeleBot):
|
||||
bot.register_message_handler(hello_handler, func=lambda message: message.text == 'Hello', pass_bot=True)
|
||||
bot.register_message_handler(echo_handler, pass_bot=True)
|
56
examples/asynchronous_telebot/multibot/main.py
Normal file
56
examples/asynchronous_telebot/multibot/main.py
Normal file
@ -0,0 +1,56 @@
|
||||
import asyncio
|
||||
|
||||
from aiohttp import web
|
||||
from telebot import types, util
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from handlers import register_handlers
|
||||
|
||||
import config
|
||||
|
||||
main_bot = AsyncTeleBot(config.MAIN_BOT_TOKEN)
|
||||
app = web.Application()
|
||||
tokens = {config.MAIN_BOT_TOKEN: True}
|
||||
|
||||
|
||||
async def webhook(request):
|
||||
token = request.match_info.get('token')
|
||||
if not tokens.get(token):
|
||||
return web.Response(status=404)
|
||||
|
||||
if request.headers.get('content-type') != 'application/json':
|
||||
return web.Response(status=403)
|
||||
|
||||
json_string = await request.json()
|
||||
update = types.Update.de_json(json_string)
|
||||
if token == main_bot.token:
|
||||
await main_bot.process_new_updates([update])
|
||||
return web.Response()
|
||||
|
||||
from_update_bot = AsyncTeleBot(token)
|
||||
register_handlers(from_update_bot)
|
||||
await from_update_bot.process_new_updates([update])
|
||||
return web.Response()
|
||||
|
||||
|
||||
app.router.add_post("/" + config.WEBHOOK_PATH + "/{token}", webhook)
|
||||
|
||||
|
||||
@main_bot.message_handler(commands=['add_bot'])
|
||||
async def add_bot(message: types.Message):
|
||||
token = util.extract_arguments(message.text)
|
||||
tokens[token] = True
|
||||
|
||||
new_bot = AsyncTeleBot(token)
|
||||
await new_bot.delete_webhook()
|
||||
await new_bot.set_webhook(f"{config.WEBHOOK_HOST}/{config.WEBHOOK_PATH}/{token}")
|
||||
|
||||
await new_bot.send_message(message.chat.id, "Webhook was set.")
|
||||
|
||||
|
||||
async def main():
|
||||
await main_bot.delete_webhook()
|
||||
await main_bot.set_webhook(f"{config.WEBHOOK_HOST}/{config.WEBHOOK_PATH}/{config.MAIN_BOT_TOKEN}")
|
||||
web.run_app(app, host=config.WEBAPP_HOST, port=config.WEBAPP_PORT)
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
8
examples/asynchronous_telebot/multibot/nginx_conf.conf
Normal file
8
examples/asynchronous_telebot/multibot/nginx_conf.conf
Normal file
@ -0,0 +1,8 @@
|
||||
server {
|
||||
server_name your_domain.com;
|
||||
|
||||
location /telegram_webhook/ {
|
||||
proxy_pass http://localhost:3500;
|
||||
}
|
||||
|
||||
}
|
@ -15,4 +15,6 @@ bot.register_message_handler(start_executor, commands=['start']) # Start command
|
||||
# bot.register_edited_message_handler(*args, **kwargs)
|
||||
# And other functions..
|
||||
|
||||
bot.polling(skip_pending=True)
|
||||
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
@ -24,4 +24,6 @@ async def photos_send(message: telebot.types.Message):
|
||||
|
||||
|
||||
|
||||
bot.polling(skip_pending=True)
|
||||
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
||||
|
35
examples/asynchronous_telebot/set_command_example.py
Normal file
35
examples/asynchronous_telebot/set_command_example.py
Normal file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# This is a set_my_commands example.
|
||||
# Press on [/] button in telegram client.
|
||||
# Important, to update the command menu, be sure to exit the chat with the bot and log in again
|
||||
# Important, command for chat_id and for group have a higher priority than for all
|
||||
|
||||
import asyncio
|
||||
import telebot
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
|
||||
|
||||
API_TOKEN = '<api_token>'
|
||||
bot = AsyncTeleBot(API_TOKEN)
|
||||
|
||||
|
||||
async def main():
|
||||
# use in for delete with the necessary scope and language_code if necessary
|
||||
await bot.delete_my_commands(scope=None, language_code=None)
|
||||
|
||||
await bot.set_my_commands(
|
||||
commands=[
|
||||
telebot.types.BotCommand("command1", "command1 description"),
|
||||
telebot.types.BotCommand("command2", "command2 description")
|
||||
],
|
||||
# scope=telebot.types.BotCommandScopeChat(12345678) # use for personal command menu for users
|
||||
# scope=telebot.types.BotCommandScopeAllPrivateChats() # use for all private chats
|
||||
)
|
||||
|
||||
cmd = await bot.get_my_commands(scope=None, language_code=None)
|
||||
print([c.to_json() for c in cmd])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
@ -10,4 +10,6 @@ async def send_welcome(message):
|
||||
async def echo_all(message):
|
||||
await bot.reply_to(message, message.text)
|
||||
|
||||
bot.polling(skip_pending=True)# Skip pending skips old updates
|
||||
|
||||
import asyncio
|
||||
asyncio.run(bot.polling(skip_pending=True)) # to skip updates
|
||||
|
52
examples/asynchronous_telebot/timer_bot_async.py
Normal file
52
examples/asynchronous_telebot/timer_bot_async.py
Normal file
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# This is a simple bot with schedule timer
|
||||
# https://github.com/ibrb/python-aioschedule
|
||||
# https://schedule.readthedocs.io
|
||||
|
||||
import asyncio
|
||||
import aioschedule
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
|
||||
API_TOKEN = '<api_token>'
|
||||
bot = AsyncTeleBot(API_TOKEN)
|
||||
|
||||
|
||||
async def beep(chat_id) -> None:
|
||||
"""Send the beep message."""
|
||||
await bot.send_message(chat_id, text='Beep!')
|
||||
aioschedule.clear(chat_id) # return schedule.CancelJob not working in aioschedule use tag for delete
|
||||
|
||||
|
||||
@bot.message_handler(commands=['help', 'start'])
|
||||
async def send_welcome(message):
|
||||
await bot.reply_to(message, "Hi! Use /set <seconds> to set a timer")
|
||||
|
||||
|
||||
@bot.message_handler(commands=['set'])
|
||||
async def set_timer(message):
|
||||
args = message.text.split()
|
||||
if len(args) > 1 and args[1].isdigit():
|
||||
sec = int(args[1])
|
||||
aioschedule.every(sec).seconds.do(beep, message.chat.id).tag(message.chat.id)
|
||||
else:
|
||||
await bot.reply_to(message, 'Usage: /set <seconds>')
|
||||
|
||||
|
||||
@bot.message_handler(commands=['unset'])
|
||||
def unset_timer(message):
|
||||
aioschedule.clean(message.chat.id)
|
||||
|
||||
|
||||
async def scheduler():
|
||||
while True:
|
||||
await aioschedule.run_pending()
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
async def main():
|
||||
await asyncio.gather(bot.infinity_polling(), scheduler())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
@ -11,4 +11,6 @@ async def update_listener(messages):
|
||||
|
||||
bot.set_update_listener(update_listener)
|
||||
|
||||
bot.polling()
|
||||
|
||||
import asyncio
|
||||
asyncio.run(bot.polling())
|
@ -0,0 +1,25 @@
|
||||
import telebot
|
||||
from telebot import types, AdvancedCustomFilter
|
||||
from telebot.callback_data import CallbackData, CallbackDataFilter
|
||||
|
||||
calendar_factory = CallbackData("year", "month", prefix="calendar")
|
||||
calendar_zoom = CallbackData("year", prefix="calendar_zoom")
|
||||
|
||||
|
||||
class CalendarCallbackFilter(AdvancedCustomFilter):
|
||||
key = 'calendar_config'
|
||||
|
||||
def check(self, call: types.CallbackQuery, config: CallbackDataFilter):
|
||||
return config.check(query=call)
|
||||
|
||||
|
||||
class CalendarZoomCallbackFilter(AdvancedCustomFilter):
|
||||
key = 'calendar_zoom_config'
|
||||
|
||||
def check(self, call: types.CallbackQuery, config: CallbackDataFilter):
|
||||
return config.check(query=call)
|
||||
|
||||
|
||||
def bind_filters(bot: telebot.TeleBot):
|
||||
bot.add_custom_filter(CalendarCallbackFilter())
|
||||
bot.add_custom_filter(CalendarZoomCallbackFilter())
|
@ -0,0 +1,92 @@
|
||||
import calendar
|
||||
from datetime import date, timedelta
|
||||
|
||||
from examples.callback_data_examples.advanced_calendar_example.filters import calendar_factory, calendar_zoom
|
||||
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
EMTPY_FIELD = '1'
|
||||
WEEK_DAYS = [calendar.day_abbr[i] for i in range(7)]
|
||||
MONTHS = [(i, calendar.month_name[i]) for i in range(1, 13)]
|
||||
|
||||
|
||||
def generate_calendar_days(year: int, month: int):
|
||||
keyboard = InlineKeyboardMarkup(row_width=7)
|
||||
today = date.today()
|
||||
|
||||
keyboard.add(
|
||||
InlineKeyboardButton(
|
||||
text=date(year=year, month=month, day=1).strftime('%b %Y'),
|
||||
callback_data=EMTPY_FIELD
|
||||
)
|
||||
)
|
||||
keyboard.add(*[
|
||||
InlineKeyboardButton(
|
||||
text=day,
|
||||
callback_data=EMTPY_FIELD
|
||||
)
|
||||
for day in WEEK_DAYS
|
||||
])
|
||||
|
||||
for week in calendar.Calendar().monthdayscalendar(year=year, month=month):
|
||||
week_buttons = []
|
||||
for day in week:
|
||||
day_name = ' '
|
||||
if day == today.day and today.year == year and today.month == month:
|
||||
day_name = '🔘'
|
||||
elif day != 0:
|
||||
day_name = str(day)
|
||||
week_buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text=day_name,
|
||||
callback_data=EMTPY_FIELD
|
||||
)
|
||||
)
|
||||
keyboard.add(*week_buttons)
|
||||
|
||||
previous_date = date(year=year, month=month, day=1) - timedelta(days=1)
|
||||
next_date = date(year=year, month=month, day=1) + timedelta(days=31)
|
||||
|
||||
keyboard.add(
|
||||
InlineKeyboardButton(
|
||||
text='Previous month',
|
||||
callback_data=calendar_factory.new(year=previous_date.year, month=previous_date.month)
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text='Zoom out',
|
||||
callback_data=calendar_zoom.new(year=year)
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text='Next month',
|
||||
callback_data=calendar_factory.new(year=next_date.year, month=next_date.month)
|
||||
),
|
||||
)
|
||||
|
||||
return keyboard
|
||||
|
||||
|
||||
def generate_calendar_months(year: int):
|
||||
keyboard = InlineKeyboardMarkup(row_width=3)
|
||||
keyboard.add(
|
||||
InlineKeyboardButton(
|
||||
text=date(year=year, month=1, day=1).strftime('Year %Y'),
|
||||
callback_data=EMTPY_FIELD
|
||||
)
|
||||
)
|
||||
keyboard.add(*[
|
||||
InlineKeyboardButton(
|
||||
text=month,
|
||||
callback_data=calendar_factory.new(year=year, month=month_number)
|
||||
)
|
||||
for month_number, month in MONTHS
|
||||
])
|
||||
keyboard.add(
|
||||
InlineKeyboardButton(
|
||||
text='Previous year',
|
||||
callback_data=calendar_zoom.new(year=year - 1)
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text='Next year',
|
||||
callback_data=calendar_zoom.new(year=year + 1)
|
||||
)
|
||||
)
|
||||
return keyboard
|
@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This Example will show you an advanced usage of CallbackData.
|
||||
In this example calendar was implemented
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
|
||||
from examples.callback_data_examples.advanced_calendar_example.keyboards import generate_calendar_days, \
|
||||
generate_calendar_months, EMTPY_FIELD
|
||||
from filters import calendar_factory, calendar_zoom, bind_filters
|
||||
from telebot import types, TeleBot
|
||||
|
||||
API_TOKEN = ''
|
||||
bot = TeleBot(API_TOKEN)
|
||||
|
||||
|
||||
@bot.message_handler(commands='start')
|
||||
def start_command_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id,
|
||||
f"Hello {message.from_user.first_name}. This bot is an example of calendar keyboard."
|
||||
"\nPress /calendar to see it.")
|
||||
|
||||
|
||||
@bot.message_handler(commands='calendar')
|
||||
def calendar_command_handler(message: types.Message):
|
||||
now = date.today()
|
||||
bot.send_message(message.chat.id, 'Calendar', reply_markup=generate_calendar_days(year=now.year, month=now.month))
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, calendar_config=calendar_factory.filter())
|
||||
def calendar_action_handler(call: types.CallbackQuery):
|
||||
callback_data: dict = calendar_factory.parse(callback_data=call.data)
|
||||
year, month = int(callback_data['year']), int(callback_data['month'])
|
||||
|
||||
bot.edit_message_reply_markup(call.message.chat.id, call.message.id,
|
||||
reply_markup=generate_calendar_days(year=year, month=month))
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, calendar_zoom_config=calendar_zoom.filter())
|
||||
def calendar_zoom_out_handler(call: types.CallbackQuery):
|
||||
callback_data: dict = calendar_zoom.parse(callback_data=call.data)
|
||||
year = int(callback_data.get('year'))
|
||||
|
||||
bot.edit_message_reply_markup(call.message.chat.id, call.message.id,
|
||||
reply_markup=generate_calendar_months(year=year))
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=lambda call: call.data == EMTPY_FIELD)
|
||||
def callback_empty_field_handler(call: types.CallbackQuery):
|
||||
bot.answer_callback_query(call.id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bind_filters(bot)
|
||||
bot.infinity_polling()
|
@ -23,8 +23,8 @@ def my_chat_m(message: types.ChatMemberUpdated):
|
||||
#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'
|
||||
#'proximity_alert_triggered', 'video_chat_scheduled', 'video_chat_started', 'video_chat_ended',
|
||||
#'video_chat_participants_invited', 'message_auto_delete_timer_changed'
|
||||
# this handler deletes service messages
|
||||
|
||||
@bot.message_handler(content_types=util.content_type_service)
|
||||
|
33
examples/create_invite_link.py
Normal file
33
examples/create_invite_link.py
Normal file
@ -0,0 +1,33 @@
|
||||
import telebot
|
||||
from time import sleep, time
|
||||
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton #Only for creating Inline Buttons, not necessary for creating Invite Links
|
||||
|
||||
Token = "api_token" #Your Bot Access Token
|
||||
Group_ID = -1234567890 #Group ID for which invite link is to be created
|
||||
|
||||
bot = telebot.TeleBot(Token, parse_mode="HTML")
|
||||
|
||||
#/start command message
|
||||
@bot.message_handler(commands=['start'])
|
||||
def startmsg(msg):
|
||||
bot.reply_to(msg, "Hey there, I'm a bot made by pyTelegramBotAPI!")
|
||||
|
||||
#Get notified of incoming members in group
|
||||
@bot.message_handler(content_types=['new_chat_members'])
|
||||
def newmember(msg):
|
||||
#Create an invite link class that contains info about the created invite link using create_chat_invite_link() with parameters
|
||||
invite = bot.create_chat_invite_link(Group_ID, member_limit=1, expire_date=int(time())+45) #Here, the link will auto-expire in 45 seconds
|
||||
InviteLink = invite.invite_link #Get the actual invite link from 'invite' class
|
||||
|
||||
mrkplink = InlineKeyboardMarkup() #Created Inline Keyboard Markup
|
||||
mrkplink.add(InlineKeyboardButton("Join our group 🚀", url=InviteLink)) #Added Invite Link to Inline Keyboard
|
||||
|
||||
bot.send_message(msg.chat.id, f"Hey there {msg.from_user.first_name}, Click the link below to join our Official Group.", reply_markup=mrkplink)
|
||||
|
||||
#This will send a message with the newly-created invite link as markup button.
|
||||
#The member limit will be 1 and expiring time will be 45 sec.
|
||||
|
||||
|
||||
|
||||
|
||||
bot.infinity_polling()
|
125
examples/custom_filters/advanced_text_filter.py
Normal file
125
examples/custom_filters/advanced_text_filter.py
Normal file
@ -0,0 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This Example will show you usage of TextFilter
|
||||
In this example you will see how to use TextFilter
|
||||
with (message_handler, callback_query_handler, poll_handler)
|
||||
"""
|
||||
|
||||
from telebot import TeleBot, types
|
||||
from telebot.custom_filters import TextFilter, TextMatchFilter, IsReplyFilter
|
||||
|
||||
bot = TeleBot("")
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(equals='hello'))
|
||||
def hello_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(equals='hello', ignore_case=True))
|
||||
def hello_handler_ignore_case(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text + ' ignore case')
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(contains=['good', 'bad']))
|
||||
def contains_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(contains=['good', 'bad'], ignore_case=True))
|
||||
def contains_handler_ignore_case(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text + ' ignore case')
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(starts_with='st')) # stArk, steve, stONE
|
||||
def starts_with_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(starts_with='st', ignore_case=True)) # STark, sTeve, stONE
|
||||
def starts_with_handler_ignore_case(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text + ' ignore case')
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(ends_with='ay')) # wednesday, SUNday, WeekDay
|
||||
def ends_with_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(ends_with='ay', ignore_case=True)) # wednesdAY, sundAy, WeekdaY
|
||||
def ends_with_handler_ignore_case(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text + ' ignore case')
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(equals='/callback'))
|
||||
def send_callback(message: types.Message):
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
keyboard=[
|
||||
[types.InlineKeyboardButton(text='callback data', callback_data='example')],
|
||||
[types.InlineKeyboardButton(text='ignore case callback data', callback_data='ExAmPLe')]
|
||||
]
|
||||
)
|
||||
bot.send_message(message.chat.id, message.text, reply_markup=keyboard)
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=TextFilter(equals='example'))
|
||||
def callback_query_handler(call: types.CallbackQuery):
|
||||
bot.answer_callback_query(call.id, call.data, show_alert=True)
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=TextFilter(equals='example', ignore_case=True))
|
||||
def callback_query_handler_ignore_case(call: types.CallbackQuery):
|
||||
bot.answer_callback_query(call.id, call.data + " ignore case", show_alert=True)
|
||||
|
||||
|
||||
@bot.message_handler(text=TextFilter(equals='/poll'))
|
||||
def send_poll(message: types.Message):
|
||||
bot.send_poll(message.chat.id, question='When do you prefer to work?', options=['Morning', 'Night'])
|
||||
bot.send_poll(message.chat.id, question='WHEN DO you pRefeR to worK?', options=['Morning', 'Night'])
|
||||
|
||||
|
||||
@bot.poll_handler(func=None, text=TextFilter(equals='When do you prefer to work?'))
|
||||
def poll_question_handler(poll: types.Poll):
|
||||
print(poll.question)
|
||||
|
||||
|
||||
@bot.poll_handler(func=None, text=TextFilter(equals='When do you prefer to work?', ignore_case=True))
|
||||
def poll_question_handler_ignore_case(poll: types.Poll):
|
||||
print(poll.question + ' ignore case')
|
||||
|
||||
|
||||
# either hi or contains one of (привет, salom)
|
||||
@bot.message_handler(text=TextFilter(equals="hi", contains=('привет', 'salom'), ignore_case=True))
|
||||
def multiple_patterns_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
# starts with one of (mi, mea) for ex. minor, milk, meal, meat
|
||||
@bot.message_handler(text=TextFilter(starts_with=['mi', 'mea'], ignore_case=True))
|
||||
def multiple_starts_with_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
# ends with one of (es, on) for ex. Jones, Davies, Johnson, Wilson
|
||||
@bot.message_handler(text=TextFilter(ends_with=['es', 'on'], ignore_case=True))
|
||||
def multiple_ends_with_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
# !ban /ban .ban !бан /бан .бан
|
||||
@bot.message_handler(is_reply=True,
|
||||
text=TextFilter(starts_with=('!', '/', '.'), ends_with=['ban', 'бан'], ignore_case=True))
|
||||
def ban_command_handler(message: types.Message):
|
||||
if len(message.text) == 4 and message.chat.type != 'private':
|
||||
try:
|
||||
bot.ban_chat_member(message.chat.id, message.reply_to_message.from_user.id)
|
||||
bot.reply_to(message.reply_to_message, 'Banned.')
|
||||
except Exception as err:
|
||||
print(err.args)
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bot.add_custom_filter(TextMatchFilter())
|
||||
bot.add_custom_filter(IsReplyFilter())
|
||||
bot.infinity_polling()
|
@ -1,14 +1,39 @@
|
||||
import telebot
|
||||
import telebot # telebot
|
||||
|
||||
from telebot import custom_filters
|
||||
from telebot.handler_backends import State, StatesGroup #States
|
||||
|
||||
bot = telebot.TeleBot("")
|
||||
# States storage
|
||||
from telebot.storage import StateMemoryStorage
|
||||
|
||||
|
||||
class MyStates:
|
||||
name = 1
|
||||
surname = 2
|
||||
age = 3
|
||||
# Starting from version 4.4.0+, we support storages.
|
||||
# StateRedisStorage -> Redis-based storage.
|
||||
# StatePickleStorage -> Pickle-based storage.
|
||||
# For redis, you will need to install redis.
|
||||
# Pass host, db, password, or anything else,
|
||||
# if you need to change config for redis.
|
||||
# Pickle requires path. Default path is in folder .state-saves.
|
||||
# If you were using older version of pytba for pickle,
|
||||
# you need to migrate from old pickle to new by using
|
||||
# StatePickleStorage().convert_old_to_new()
|
||||
|
||||
|
||||
|
||||
# Now, you can pass storage to bot.
|
||||
state_storage = StateMemoryStorage() # you can init here another storage
|
||||
|
||||
bot = telebot.TeleBot("TOKEN",
|
||||
state_storage=state_storage)
|
||||
|
||||
|
||||
# States group.
|
||||
class MyStates(StatesGroup):
|
||||
# Just name variables differently
|
||||
name = State() # creating instances of State class is enough from now
|
||||
surname = State()
|
||||
age = State()
|
||||
|
||||
|
||||
|
||||
|
||||
@ -17,50 +42,60 @@ def start_ex(message):
|
||||
"""
|
||||
Start command. Here we are starting state
|
||||
"""
|
||||
bot.set_state(message.from_user.id, MyStates.name)
|
||||
bot.set_state(message.from_user.id, MyStates.name, message.chat.id)
|
||||
bot.send_message(message.chat.id, 'Hi, write me a name')
|
||||
|
||||
|
||||
|
||||
# Any state
|
||||
@bot.message_handler(state="*", commands='cancel')
|
||||
def any_state(message):
|
||||
"""
|
||||
Cancel state
|
||||
"""
|
||||
bot.send_message(message.chat.id, "Your state was cancelled.")
|
||||
bot.delete_state(message.from_user.id)
|
||||
bot.delete_state(message.from_user.id, message.chat.id)
|
||||
|
||||
@bot.message_handler(state=MyStates.name)
|
||||
def name_get(message):
|
||||
"""
|
||||
State 1. Will process when user's state is 1.
|
||||
State 1. Will process when user's state is MyStates.name.
|
||||
"""
|
||||
bot.send_message(message.chat.id, f'Now write me a surname')
|
||||
bot.set_state(message.from_user.id, MyStates.surname)
|
||||
with bot.retrieve_data(message.from_user.id) as data:
|
||||
bot.send_message(message.chat.id, 'Now write me a surname')
|
||||
bot.set_state(message.from_user.id, MyStates.surname, message.chat.id)
|
||||
with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
|
||||
data['name'] = message.text
|
||||
|
||||
|
||||
@bot.message_handler(state=MyStates.surname)
|
||||
def ask_age(message):
|
||||
"""
|
||||
State 2. Will process when user's state is 2.
|
||||
State 2. Will process when user's state is MyStates.surname.
|
||||
"""
|
||||
bot.send_message(message.chat.id, "What is your age?")
|
||||
bot.set_state(message.from_user.id, MyStates.age)
|
||||
with bot.retrieve_data(message.from_user.id) as data:
|
||||
bot.set_state(message.from_user.id, MyStates.age, message.chat.id)
|
||||
with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
|
||||
data['surname'] = message.text
|
||||
|
||||
# result
|
||||
@bot.message_handler(state=MyStates.age, is_digit=True)
|
||||
def ready_for_answer(message):
|
||||
with bot.retrieve_data(message.from_user.id) as data:
|
||||
bot.send_message(message.chat.id, "Ready, take a look:\n<b>Name: {name}\nSurname: {surname}\nAge: {age}</b>".format(name=data['name'], surname=data['surname'], age=message.text), parse_mode="html")
|
||||
bot.delete_state(message.from_user.id)
|
||||
"""
|
||||
State 3. Will process when user's state is MyStates.age.
|
||||
"""
|
||||
with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
|
||||
msg = ("Ready, take a look:\n<b>"
|
||||
f"Name: {data['name']}\n"
|
||||
f"Surname: {data['surname']}\n"
|
||||
f"Age: {message.text}</b>")
|
||||
bot.send_message(message.chat.id, msg, parse_mode="html")
|
||||
bot.delete_state(message.from_user.id, message.chat.id)
|
||||
|
||||
#incorrect number
|
||||
@bot.message_handler(state=MyStates.age, is_digit=False)
|
||||
def age_incorrect(message):
|
||||
"""
|
||||
Wrong response for MyStates.age
|
||||
"""
|
||||
bot.send_message(message.chat.id, 'Looks like you are submitting a string in the field age. Please enter a number')
|
||||
|
||||
# register filters
|
||||
@ -68,7 +103,4 @@ def age_incorrect(message):
|
||||
bot.add_custom_filter(custom_filters.StateFilter(bot))
|
||||
bot.add_custom_filter(custom_filters.IsDigitFilter())
|
||||
|
||||
# set saving states into file.
|
||||
bot.enable_saving_states() # you can delete this if you do not need to save states
|
||||
|
||||
bot.infinity_polling(skip_pending=True)
|
||||
bot.infinity_polling(skip_pending=True)
|
||||
|
53
examples/formatting_example.py
Normal file
53
examples/formatting_example.py
Normal file
@ -0,0 +1,53 @@
|
||||
from telebot import TeleBot
|
||||
from telebot import formatting
|
||||
|
||||
bot = TeleBot('TOKEN')
|
||||
|
||||
|
||||
@bot.message_handler(commands=['start'])
|
||||
def start_message(message):
|
||||
bot.send_message(
|
||||
message.chat.id,
|
||||
# function which connects all strings
|
||||
formatting.format_text(
|
||||
formatting.mbold(message.from_user.first_name),
|
||||
formatting.mitalic(message.from_user.first_name),
|
||||
formatting.munderline(message.from_user.first_name),
|
||||
formatting.mstrikethrough(message.from_user.first_name),
|
||||
formatting.mcode(message.from_user.first_name),
|
||||
separator=" " # separator separates all strings
|
||||
),
|
||||
parse_mode='MarkdownV2'
|
||||
)
|
||||
|
||||
# just a bold text using markdownv2
|
||||
bot.send_message(
|
||||
message.chat.id,
|
||||
formatting.mbold(message.from_user.first_name),
|
||||
parse_mode='MarkdownV2'
|
||||
)
|
||||
|
||||
# html
|
||||
bot.send_message(
|
||||
message.chat.id,
|
||||
formatting.format_text(
|
||||
formatting.hbold(message.from_user.first_name),
|
||||
formatting.hitalic(message.from_user.first_name),
|
||||
formatting.hunderline(message.from_user.first_name),
|
||||
formatting.hstrikethrough(message.from_user.first_name),
|
||||
formatting.hcode(message.from_user.first_name),
|
||||
# hide_link is only for html
|
||||
formatting.hide_link('https://telegra.ph/file/c158e3a6e2a26a160b253.jpg'),
|
||||
separator=" "
|
||||
),
|
||||
parse_mode='HTML'
|
||||
)
|
||||
|
||||
# just a bold text in html
|
||||
bot.send_message(
|
||||
message.chat.id,
|
||||
formatting.hbold(message.from_user.first_name),
|
||||
parse_mode='HTML'
|
||||
)
|
||||
|
||||
bot.infinity_polling()
|
35
examples/middleware/README.md
Normal file
35
examples/middleware/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Middlewares
|
||||
|
||||
## Type of middlewares in pyTelegramBotAPI
|
||||
Currently, synchronous version of pyTelegramBotAPI has two types of middlewares:
|
||||
|
||||
- Class-based middlewares
|
||||
- Function-based middlewares
|
||||
|
||||
## Purpose of middlewares
|
||||
Middlewares are designed to get update before handler's execution.
|
||||
|
||||
## Class-based middlewares
|
||||
This type of middleware has more functionality compared to function-based one.
|
||||
|
||||
Class based middleware should be instance of `telebot.handler_backends.BaseMiddleware.`
|
||||
|
||||
Each middleware should have 2 main functions:
|
||||
|
||||
`pre_process` -> is a method of class which receives update, and data.
|
||||
|
||||
Data - is a dictionary, which could be passed right to handler, and `post_process` function.
|
||||
|
||||
`post_process` -> is a function of class which receives update, data, and exception, that happened in handler. If handler was executed correctly - then exception will equal to None.
|
||||
|
||||
## Function-based middlewares
|
||||
To use function-based middleware, you should set `apihelper.ENABLE_MIDDLEWARE = True`.
|
||||
This type of middleware is created by using a decorator for middleware.
|
||||
With this type middleware, you can retrieve update immediately after update came. You should set update_types as well.
|
||||
|
||||
## Why class-based middlewares are better?
|
||||
- You can pass data between post, pre_process functions, and handler.
|
||||
- If there is an exception in handler, you will get exception parameter with exception class in post_process.
|
||||
- Has post_process -> method which works after the handler's execution.
|
||||
|
||||
## Take a look at examples for more.
|
39
examples/middleware/class_based/antiflood_middleware.py
Normal file
39
examples/middleware/class_based/antiflood_middleware.py
Normal file
@ -0,0 +1,39 @@
|
||||
# Just a little example of middleware handlers
|
||||
|
||||
from telebot.handler_backends import BaseMiddleware
|
||||
from telebot import TeleBot
|
||||
from telebot.handler_backends import CancelUpdate
|
||||
bot = TeleBot('TOKEN',
|
||||
use_class_middlewares=True) # if you don't set it to true, middlewares won't work
|
||||
|
||||
|
||||
class SimpleMiddleware(BaseMiddleware):
|
||||
def __init__(self, limit) -> None:
|
||||
self.last_time = {}
|
||||
self.limit = limit
|
||||
self.update_types = ['message']
|
||||
# Always specify update types, otherwise middlewares won't work
|
||||
|
||||
|
||||
def pre_process(self, message, data):
|
||||
if not message.from_user.id in self.last_time:
|
||||
# User is not in a dict, so lets add and cancel this function
|
||||
self.last_time[message.from_user.id] = message.date
|
||||
return
|
||||
if message.date - self.last_time[message.from_user.id] < self.limit:
|
||||
# User is flooding
|
||||
bot.send_message(message.chat.id, 'You are making request too often')
|
||||
return CancelUpdate()
|
||||
self.last_time[message.from_user.id] = message.date
|
||||
|
||||
|
||||
def post_process(self, message, data, exception):
|
||||
pass
|
||||
|
||||
bot.setup_middleware(SimpleMiddleware(2))
|
||||
|
||||
@bot.message_handler(commands=['start'])
|
||||
def start(message): # you don't have to put data in handler.
|
||||
bot.send_message(message.chat.id, 'Hello!')
|
||||
|
||||
bot.infinity_polling()
|
32
examples/middleware/class_based/basic_example.py
Normal file
32
examples/middleware/class_based/basic_example.py
Normal file
@ -0,0 +1,32 @@
|
||||
from telebot import TeleBot
|
||||
from telebot.handler_backends import BaseMiddleware
|
||||
|
||||
bot = TeleBot('TOKEN', use_class_middlewares=True) # set use_class_middlewares to True!
|
||||
# otherwise, class-based middlewares won't execute.
|
||||
|
||||
# You can use this classes for cancelling update or skipping handler:
|
||||
# from telebot.handler_backends import CancelUpdate, SkipHandler
|
||||
|
||||
class Middleware(BaseMiddleware):
|
||||
def __init__(self):
|
||||
self.update_types = ['message']
|
||||
def pre_process(self, message, data):
|
||||
data['foo'] = 'Hello' # just for example
|
||||
# we edited the data. now, this data is passed to handler.
|
||||
# return SkipHandler() -> this will skip handler
|
||||
# return CancelUpdate() -> this will cancel update
|
||||
def post_process(self, message, data, exception=None):
|
||||
print(data['foo'])
|
||||
if exception: # check for exception
|
||||
print(exception)
|
||||
|
||||
@bot.message_handler(commands=['start'])
|
||||
def start(message, data: dict): # you don't have to put data parameter in handler if you don't need it.
|
||||
bot.send_message(message.chat.id, data['foo'])
|
||||
data['foo'] = 'Processed' # we changed value of data.. this data is now passed to post_process.
|
||||
|
||||
|
||||
# Setup middleware
|
||||
bot.setup_middleware(Middleware())
|
||||
|
||||
bot.infinity_polling()
|
@ -0,0 +1,121 @@
|
||||
import gettext
|
||||
import os
|
||||
import threading
|
||||
|
||||
from telebot.handler_backends import BaseMiddleware
|
||||
|
||||
try:
|
||||
from babel.support import LazyProxy
|
||||
|
||||
babel_imported = True
|
||||
except ImportError:
|
||||
babel_imported = False
|
||||
|
||||
|
||||
class I18N(BaseMiddleware):
|
||||
"""
|
||||
This middleware provides high-level tool for internationalization
|
||||
It is based on gettext util.
|
||||
"""
|
||||
|
||||
context_lang = threading.local()
|
||||
|
||||
def __init__(self, translations_path, domain_name: str):
|
||||
super().__init__()
|
||||
self.update_types = self.process_update_types()
|
||||
|
||||
self.path = translations_path
|
||||
self.domain = domain_name
|
||||
self.translations = self.find_translations()
|
||||
|
||||
@property
|
||||
def available_translations(self):
|
||||
return list(self.translations)
|
||||
|
||||
def gettext(self, text: str, lang: str = None):
|
||||
"""
|
||||
Singular translations
|
||||
"""
|
||||
|
||||
if lang is None:
|
||||
lang = self.context_lang.language
|
||||
|
||||
if lang not in self.translations:
|
||||
return text
|
||||
|
||||
translator = self.translations[lang]
|
||||
return translator.gettext(text)
|
||||
|
||||
def ngettext(self, singular: str, plural: str, lang: str = None, n=1):
|
||||
"""
|
||||
Plural translations
|
||||
"""
|
||||
if lang is None:
|
||||
lang = self.context_lang.language
|
||||
|
||||
if lang not in self.translations:
|
||||
if n == 1:
|
||||
return singular
|
||||
return plural
|
||||
|
||||
translator = self.translations[lang]
|
||||
return translator.ngettext(singular, plural, n)
|
||||
|
||||
def lazy_gettext(self, text: str, lang: str = None):
|
||||
if not babel_imported:
|
||||
raise RuntimeError('babel module is not imported. Check that you installed it.')
|
||||
return LazyProxy(self.gettext, text, lang, enable_cache=False)
|
||||
|
||||
def lazy_ngettext(self, singular: str, plural: str, lang: str = None, n=1):
|
||||
if not babel_imported:
|
||||
raise RuntimeError('babel module is not imported. Check that you installed it.')
|
||||
return LazyProxy(self.ngettext, singular, plural, lang, n, enable_cache=False)
|
||||
|
||||
def get_user_language(self, obj):
|
||||
"""
|
||||
You need to override this method and return user language
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def process_update_types(self) -> list:
|
||||
"""
|
||||
You need to override this method and return any update types which you want to be processed
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def pre_process(self, message, data):
|
||||
"""
|
||||
context language variable will be set each time when update from 'process_update_types' comes
|
||||
value is the result of 'get_user_language' method
|
||||
"""
|
||||
|
||||
self.context_lang.language = self.get_user_language(obj=message)
|
||||
|
||||
def post_process(self, message, data, exception):
|
||||
pass
|
||||
|
||||
def find_translations(self):
|
||||
"""
|
||||
Looks for translations with passed 'domain' in passed 'path'
|
||||
"""
|
||||
if not os.path.exists(self.path):
|
||||
raise RuntimeError(f"Translations directory by path: {self.path!r} was not found")
|
||||
|
||||
result = {}
|
||||
|
||||
for name in os.listdir(self.path):
|
||||
translations_path = os.path.join(self.path, name, 'LC_MESSAGES')
|
||||
|
||||
if not os.path.isdir(translations_path):
|
||||
continue
|
||||
|
||||
po_file = os.path.join(translations_path, self.domain + '.po')
|
||||
mo_file = po_file[:-2] + 'mo'
|
||||
|
||||
if os.path.isfile(po_file) and not os.path.isfile(mo_file):
|
||||
raise FileNotFoundError(f"Translations for: {name!r} were not compiled!")
|
||||
|
||||
with open(mo_file, 'rb') as file:
|
||||
result[name] = gettext.GNUTranslations(file)
|
||||
|
||||
return result
|
34
examples/middleware/class_based/i18n_middleware/keyboards.py
Normal file
34
examples/middleware/class_based/i18n_middleware/keyboards.py
Normal file
@ -0,0 +1,34 @@
|
||||
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardMarkup, KeyboardButton
|
||||
|
||||
|
||||
def languages_keyboard():
|
||||
return InlineKeyboardMarkup(
|
||||
keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(text="English", callback_data='en'),
|
||||
InlineKeyboardButton(text="Русский", callback_data='ru'),
|
||||
InlineKeyboardButton(text="O'zbekcha", callback_data='uz_Latn')
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def clicker_keyboard(_):
|
||||
return InlineKeyboardMarkup(
|
||||
keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(text=_("click"), callback_data='click'),
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def menu_keyboard(_):
|
||||
keyboard = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
keyboard.add(
|
||||
KeyboardButton(text=_("My user id")),
|
||||
KeyboardButton(text=_("My user name")),
|
||||
KeyboardButton(text=_("My first name"))
|
||||
)
|
||||
|
||||
return keyboard
|
@ -0,0 +1,81 @@
|
||||
# English translations for PROJECT.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2022-02-19 18:37+0500\n"
|
||||
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
"Language-Team: en <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: keyboards.py:20
|
||||
msgid "click"
|
||||
msgstr ""
|
||||
|
||||
#: keyboards.py:29
|
||||
msgid "My user id"
|
||||
msgstr ""
|
||||
|
||||
#: keyboards.py:30
|
||||
msgid "My user name"
|
||||
msgstr ""
|
||||
|
||||
#: keyboards.py:31
|
||||
msgid "My first name"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:97
|
||||
msgid ""
|
||||
"Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n"
|
||||
"\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example\n"
|
||||
"/menu - text menu example"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:121
|
||||
msgid "Language has been changed"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:130 main.py:150
|
||||
#, fuzzy
|
||||
msgid "You have {number} click"
|
||||
msgid_plural "You have {number} clicks"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: main.py:135 main.py:155
|
||||
msgid ""
|
||||
"This is clicker.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:163
|
||||
msgid "This is ReplyKeyboardMarkup menu example in multilanguage bot."
|
||||
msgstr ""
|
||||
|
||||
#: main.py:203
|
||||
msgid "Seems you confused language"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Hello, {user_fist_name}!\n"
|
||||
#~ "This is the example of multilanguage bot.\n"
|
||||
#~ "Available commands:\n"
|
||||
#~ "\n"
|
||||
#~ "/lang - change your language\n"
|
||||
#~ "/plural - pluralization example"
|
||||
#~ msgstr ""
|
||||
|
@ -0,0 +1,82 @@
|
||||
# Russian translations for PROJECT.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2022-02-19 18:37+0500\n"
|
||||
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: ru\n"
|
||||
"Language-Team: ru <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: keyboards.py:20
|
||||
msgid "click"
|
||||
msgstr "Клик"
|
||||
|
||||
#: keyboards.py:29
|
||||
msgid "My user id"
|
||||
msgstr "Мой user id"
|
||||
|
||||
#: keyboards.py:30
|
||||
msgid "My user name"
|
||||
msgstr "Мой user name"
|
||||
|
||||
#: keyboards.py:31
|
||||
msgid "My first name"
|
||||
msgstr "Мой first name"
|
||||
|
||||
#: main.py:97
|
||||
msgid ""
|
||||
"Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n"
|
||||
"\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example\n"
|
||||
"/menu - text menu example"
|
||||
msgstr ""
|
||||
"Привет, {user_fist_name}!\n"
|
||||
"Это пример мультиязычного бота.\n"
|
||||
"Доступные команды:\n"
|
||||
"\n"
|
||||
"/lang - изменить язык\n"
|
||||
"/plural - пример плюрализации\n"
|
||||
"/menu - Пример текстового меню"
|
||||
|
||||
#: main.py:121
|
||||
msgid "Language has been changed"
|
||||
msgstr "Язык был сменён"
|
||||
|
||||
#: main.py:130 main.py:150
|
||||
msgid "You have {number} click"
|
||||
msgid_plural "You have {number} clicks"
|
||||
msgstr[0] "У вас {number} клик"
|
||||
msgstr[1] "У вас {number} клика"
|
||||
msgstr[2] "У вас {number} кликов"
|
||||
|
||||
#: main.py:135 main.py:155
|
||||
msgid ""
|
||||
"This is clicker.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"Это кликер.\n"
|
||||
"\n"
|
||||
|
||||
#: main.py:163
|
||||
msgid "This is ReplyKeyboardMarkup menu example in multilanguage bot."
|
||||
msgstr "Это пример ReplyKeyboardMarkup меню в мультиязычном боте."
|
||||
|
||||
#: main.py:203
|
||||
msgid "Seems you confused language"
|
||||
msgstr "Кажется, вы перепутали язык"
|
||||
|
@ -0,0 +1,80 @@
|
||||
# Uzbek (Latin) translations for PROJECT.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2022-02-19 18:37+0500\n"
|
||||
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: uz_Latn\n"
|
||||
"Language-Team: uz_Latn <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: keyboards.py:20
|
||||
msgid "click"
|
||||
msgstr "clik"
|
||||
|
||||
#: keyboards.py:29
|
||||
msgid "My user id"
|
||||
msgstr "Mani user id"
|
||||
|
||||
#: keyboards.py:30
|
||||
msgid "My user name"
|
||||
msgstr "Mani user name"
|
||||
|
||||
#: keyboards.py:31
|
||||
msgid "My first name"
|
||||
msgstr "Mani first name"
|
||||
|
||||
#: main.py:97
|
||||
msgid ""
|
||||
"Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n"
|
||||
"\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example\n"
|
||||
"/menu - text menu example"
|
||||
msgstr ""
|
||||
"Salom, {user_fist_name}!\n"
|
||||
"Bu multilanguage bot misoli.\n"
|
||||
"Mavjud buyruqlar:\n"
|
||||
"\n"
|
||||
"/lang - tilni ozgartirish\n"
|
||||
"/plural - pluralizatsiya misoli\n"
|
||||
"/menu - text menu misoli"
|
||||
|
||||
#: main.py:121
|
||||
msgid "Language has been changed"
|
||||
msgstr "Til ozgartirildi"
|
||||
|
||||
#: main.py:130 main.py:150
|
||||
msgid "You have {number} click"
|
||||
msgid_plural "You have {number} clicks"
|
||||
msgstr[0] "Sizda {number}ta clik"
|
||||
msgstr[1] "Sizda {number}ta clik"
|
||||
|
||||
#: main.py:135 main.py:155
|
||||
msgid ""
|
||||
"This is clicker.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"Bu clicker.\n"
|
||||
"\n"
|
||||
|
||||
#: main.py:163
|
||||
msgid "This is ReplyKeyboardMarkup menu example in multilanguage bot."
|
||||
msgstr "Bu multilanguage bot da replykeyboardmarkup menyu misoli."
|
||||
|
||||
#: main.py:203
|
||||
msgid "Seems you confused language"
|
||||
msgstr "Tilni adashtirdiz"
|
||||
|
211
examples/middleware/class_based/i18n_middleware/main.py
Normal file
211
examples/middleware/class_based/i18n_middleware/main.py
Normal file
@ -0,0 +1,211 @@
|
||||
"""
|
||||
In this example you will learn how to adapt your bot to different languages
|
||||
Using built-in middleware I18N.
|
||||
|
||||
You need to install babel package 'https://pypi.org/project/Babel/'
|
||||
Babel provides a command-line interface for working with message catalogs
|
||||
After installing babel package you have a script called 'pybabel'
|
||||
Too see all the commands open terminal and type 'pybabel --help'
|
||||
Full description for pybabel commands can be found here: 'https://babel.pocoo.org/en/latest/cmdline.html'
|
||||
|
||||
Create a directory 'locales' where our translations will be stored
|
||||
|
||||
First we need to extract texts:
|
||||
pybabel extract -o locales/{domain_name}.pot --input-dirs .
|
||||
{domain_name}.pot - is the file where all translations are saved
|
||||
The name of this file should be the same as domain which you pass to I18N class
|
||||
In this example domain_name will be 'messages'
|
||||
|
||||
For gettext (singular texts) we use '_' alias and it works perfect
|
||||
You may also you some alias for ngettext (plural texts) but you can face with a problem that
|
||||
your plural texts are not being extracted
|
||||
That is because by default 'pybabel extract' recognizes the following keywords:
|
||||
_, gettext, ngettext, ugettext, ungettext, dgettext, dngettext, N_
|
||||
To add your own keyword you can use '-k' flag
|
||||
In this example for 'ngettext' i will assign double underscore alias '__'
|
||||
|
||||
Full command with pluralization support will look so:
|
||||
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||
|
||||
Then create directories with translations (get list of all locales: 'pybabel --list-locales'):
|
||||
pybabel init -i locales/{domain_name}.pot -d locales -l en
|
||||
pybabel init -i locales/{domain_name}.pot -d locales -l ru
|
||||
pybabel init -i locales/{domain_name}.pot -d locales -l uz_Latn
|
||||
|
||||
Now you can translate the texts located in locales/{language}/LC_MESSAGES/{domain_name}.po
|
||||
After you translated all the texts you need to compile .po files:
|
||||
pybabel compile -d locales
|
||||
|
||||
When you delete/update your texts you also need to update them in .po files:
|
||||
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||
pybabel update -i locales/{domain_name}.pot -d locales
|
||||
- translate
|
||||
pybabel compile -d locales
|
||||
|
||||
If you have any exceptions check:
|
||||
- you have installed babel
|
||||
- translations are ready, so you just compiled it
|
||||
- in the commands above you replaced {domain_name} to messages
|
||||
- you are writing commands from correct path in terminal
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Union
|
||||
|
||||
import keyboards
|
||||
from i18n_base_middleware import I18N
|
||||
from telebot import TeleBot
|
||||
from telebot import types, StateMemoryStorage
|
||||
from telebot.custom_filters import TextMatchFilter, TextFilter
|
||||
|
||||
class I18NMiddleware(I18N):
|
||||
|
||||
def process_update_types(self) -> list:
|
||||
"""
|
||||
Here you need to return a list of update types which you want to be processed
|
||||
"""
|
||||
return ['message', 'callback_query']
|
||||
|
||||
def get_user_language(self, obj: Union[types.Message, types.CallbackQuery]):
|
||||
"""
|
||||
This method is called when new update comes (only updates which you return in 'process_update_types' method)
|
||||
Returned language will be used in 'pre_process' method of parent class
|
||||
Returned language will be set to context language variable.
|
||||
If you need to get translation with user's actual language you don't have to pass it manually
|
||||
It will be automatically passed from context language value.
|
||||
However if you need some other language you can always pass it.
|
||||
"""
|
||||
|
||||
user_id = obj.from_user.id
|
||||
|
||||
if user_id not in users_lang:
|
||||
users_lang[user_id] = 'en'
|
||||
|
||||
return users_lang[user_id]
|
||||
|
||||
|
||||
storage = StateMemoryStorage()
|
||||
bot = TeleBot("", state_storage=storage, use_class_middlewares=True)
|
||||
|
||||
i18n = I18NMiddleware(translations_path='locales', domain_name='messages')
|
||||
_ = i18n.gettext # for singular translations
|
||||
__ = i18n.ngettext # for plural translations
|
||||
|
||||
# These are example storages, do not use it in a production development
|
||||
users_lang = {}
|
||||
users_clicks = {}
|
||||
|
||||
@bot.message_handler(commands=['start'])
|
||||
def start_handler(message: types.Message):
|
||||
text = _("Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example\n"
|
||||
"/menu - text menu example")
|
||||
|
||||
# remember don't use f string for interpolation, use .format method instead
|
||||
text = text.format(user_fist_name=message.from_user.first_name)
|
||||
bot.send_message(message.from_user.id, text)
|
||||
|
||||
|
||||
@bot.message_handler(commands=['lang'])
|
||||
def change_language_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id, "Choose language\nВыберите язык\nTilni tanlang",
|
||||
reply_markup=keyboards.languages_keyboard())
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=TextFilter(contains=['en', 'ru', 'uz_Latn']))
|
||||
def language_handler(call: types.CallbackQuery):
|
||||
lang = call.data
|
||||
users_lang[call.from_user.id] = lang
|
||||
|
||||
# When you changed user language, you have to pass it manually beacause it is not changed in context
|
||||
bot.edit_message_text(_("Language has been changed", lang=lang), call.from_user.id, call.message.id)
|
||||
|
||||
|
||||
@bot.message_handler(commands=['plural'])
|
||||
def pluralization_handler(message: types.Message):
|
||||
if not users_clicks.get(message.from_user.id):
|
||||
users_clicks[message.from_user.id] = 0
|
||||
clicks = users_clicks[message.from_user.id]
|
||||
|
||||
text = __(
|
||||
singular="You have {number} click",
|
||||
plural="You have {number} clicks",
|
||||
n=clicks
|
||||
)
|
||||
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||
|
||||
bot.send_message(message.chat.id, text, reply_markup=keyboards.clicker_keyboard(_))
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=TextFilter(equals='click'))
|
||||
def click_handler(call: types.CallbackQuery):
|
||||
if not users_clicks.get(call.from_user.id):
|
||||
users_clicks[call.from_user.id] = 1
|
||||
else:
|
||||
users_clicks[call.from_user.id] += 1
|
||||
|
||||
clicks = users_clicks[call.from_user.id]
|
||||
|
||||
text = __(
|
||||
singular="You have {number} click",
|
||||
plural="You have {number} clicks",
|
||||
n=clicks
|
||||
)
|
||||
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||
|
||||
bot.edit_message_text(text, call.from_user.id, call.message.message_id,
|
||||
reply_markup=keyboards.clicker_keyboard(_))
|
||||
|
||||
|
||||
@bot.message_handler(commands=['menu'])
|
||||
def menu_handler(message: types.Message):
|
||||
text = _("This is ReplyKeyboardMarkup menu example in multilanguage bot.")
|
||||
bot.send_message(message.chat.id, text, reply_markup=keyboards.menu_keyboard(_))
|
||||
|
||||
|
||||
# For lazy tranlations
|
||||
# lazy gettext is used when you don't know user's locale
|
||||
# It can be used for example to handle text buttons in multilanguage bot
|
||||
# The actual translation will be delayed until update comes and context language is set
|
||||
l_ = i18n.lazy_gettext
|
||||
|
||||
|
||||
# Handlers below will handle text according to user's language
|
||||
@bot.message_handler(text=l_("My user id"))
|
||||
def return_user_id(message: types.Message):
|
||||
bot.send_message(message.chat.id, str(message.from_user.id))
|
||||
|
||||
|
||||
@bot.message_handler(text=l_("My user name"))
|
||||
def return_user_id(message: types.Message):
|
||||
username = message.from_user.username
|
||||
if not username:
|
||||
username = '-'
|
||||
bot.send_message(message.chat.id, username)
|
||||
|
||||
|
||||
# You can make it case-insensitive
|
||||
@bot.message_handler(text=TextFilter(equals=l_("My first name"), ignore_case=True))
|
||||
def return_user_id(message: types.Message):
|
||||
bot.send_message(message.chat.id, message.from_user.first_name)
|
||||
|
||||
|
||||
all_menu_texts = []
|
||||
for language in i18n.available_translations:
|
||||
for menu_text in ("My user id", "My user name", "My first name"):
|
||||
all_menu_texts.append(_(menu_text, language))
|
||||
|
||||
|
||||
# When user confused language. (handles all menu buttons texts)
|
||||
@bot.message_handler(text=TextFilter(contains=all_menu_texts, ignore_case=True))
|
||||
def missed_message(message: types.Message):
|
||||
bot.send_message(message.chat.id, _("Seems you confused language"), reply_markup=keyboards.menu_keyboard(_))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bot.setup_middleware(i18n)
|
||||
bot.add_custom_filter(TextMatchFilter())
|
||||
asyncio.run(bot.infinity_polling())
|
@ -0,0 +1,77 @@
|
||||
import gettext
|
||||
import os
|
||||
import threading
|
||||
|
||||
|
||||
class I18N:
|
||||
"""
|
||||
This class provides high-level tool for internationalization
|
||||
It is based on gettext util.
|
||||
"""
|
||||
|
||||
context_lang = threading.local()
|
||||
|
||||
def __init__(self, translations_path, domain_name: str):
|
||||
self.path = translations_path
|
||||
self.domain = domain_name
|
||||
self.translations = self.find_translations()
|
||||
|
||||
@property
|
||||
def available_translations(self):
|
||||
return list(self.translations)
|
||||
|
||||
def gettext(self, text: str, lang: str = None):
|
||||
"""
|
||||
Singular translations
|
||||
"""
|
||||
|
||||
if lang is None:
|
||||
lang = self.context_lang.language
|
||||
|
||||
if lang not in self.translations:
|
||||
return text
|
||||
|
||||
translator = self.translations[lang]
|
||||
return translator.gettext(text)
|
||||
|
||||
def ngettext(self, singular: str, plural: str, lang: str = None, n=1):
|
||||
"""
|
||||
Plural translations
|
||||
"""
|
||||
if lang is None:
|
||||
lang = self.context_lang.language
|
||||
|
||||
if lang not in self.translations:
|
||||
if n == 1:
|
||||
return singular
|
||||
return plural
|
||||
|
||||
translator = self.translations[lang]
|
||||
return translator.ngettext(singular, plural, n)
|
||||
|
||||
|
||||
def find_translations(self):
|
||||
"""
|
||||
Looks for translations with passed 'domain' in passed 'path'
|
||||
"""
|
||||
if not os.path.exists(self.path):
|
||||
raise RuntimeError(f"Translations directory by path: {self.path!r} was not found")
|
||||
|
||||
result = {}
|
||||
|
||||
for name in os.listdir(self.path):
|
||||
translations_path = os.path.join(self.path, name, 'LC_MESSAGES')
|
||||
|
||||
if not os.path.isdir(translations_path):
|
||||
continue
|
||||
|
||||
po_file = os.path.join(translations_path, self.domain + '.po')
|
||||
mo_file = po_file[:-2] + 'mo'
|
||||
|
||||
if os.path.isfile(po_file) and not os.path.isfile(mo_file):
|
||||
raise FileNotFoundError(f"Translations for: {name!r} were not compiled!")
|
||||
|
||||
with open(mo_file, 'rb') as file:
|
||||
result[name] = gettext.GNUTranslations(file)
|
||||
|
||||
return result
|
23
examples/middleware/function_based/i18n_gettext/keyboards.py
Normal file
23
examples/middleware/function_based/i18n_gettext/keyboards.py
Normal file
@ -0,0 +1,23 @@
|
||||
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
|
||||
def languages_keyboard():
|
||||
return InlineKeyboardMarkup(
|
||||
keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(text="English", callback_data='en'),
|
||||
InlineKeyboardButton(text="Русский", callback_data='ru'),
|
||||
InlineKeyboardButton(text="O'zbekcha", callback_data='uz_Latn')
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def clicker_keyboard(_):
|
||||
return InlineKeyboardMarkup(
|
||||
keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(text=_("click"), callback_data='click'),
|
||||
]
|
||||
]
|
||||
)
|
@ -0,0 +1,50 @@
|
||||
# English translations for PROJECT.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2022-02-18 17:54+0500\n"
|
||||
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
"Language-Team: en <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: keyboards.py:20
|
||||
msgid "click"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:78
|
||||
msgid ""
|
||||
"Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n"
|
||||
"\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:102
|
||||
msgid "Language has been changed"
|
||||
msgstr ""
|
||||
|
||||
#: main.py:114
|
||||
#, fuzzy
|
||||
msgid "You have {number} click"
|
||||
msgid_plural "You have {number} clicks"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: main.py:120
|
||||
msgid ""
|
||||
"This is clicker.\n"
|
||||
"\n"
|
||||
msgstr ""
|
@ -0,0 +1,59 @@
|
||||
# Russian translations for PROJECT.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2022-02-18 17:54+0500\n"
|
||||
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: ru\n"
|
||||
"Language-Team: ru <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: keyboards.py:20
|
||||
msgid "click"
|
||||
msgstr "Клик"
|
||||
|
||||
#: main.py:78
|
||||
msgid ""
|
||||
"Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n"
|
||||
"\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example"
|
||||
msgstr ""
|
||||
"Привет, {user_fist_name}!\n"
|
||||
"Это пример мультиязычного бота.\n"
|
||||
"Доступные команды:\n"
|
||||
"\n"
|
||||
"/lang - изменить язык\n"
|
||||
"/plural - пример плюрализации"
|
||||
|
||||
#: main.py:102
|
||||
msgid "Language has been changed"
|
||||
msgstr "Язык был сменён"
|
||||
|
||||
#: main.py:114
|
||||
msgid "You have {number} click"
|
||||
msgid_plural "You have {number} clicks"
|
||||
msgstr[0] "У вас {number} клик"
|
||||
msgstr[1] "У вас {number} клика"
|
||||
msgstr[2] "У вас {number} кликов"
|
||||
|
||||
#: main.py:120
|
||||
msgid ""
|
||||
"This is clicker.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"Это кликер.\n"
|
||||
"\n"
|
@ -0,0 +1,57 @@
|
||||
# Uzbek (Latin) translations for PROJECT.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2022-02-18 17:54+0500\n"
|
||||
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: uz_Latn\n"
|
||||
"Language-Team: uz_Latn <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: keyboards.py:20
|
||||
msgid "click"
|
||||
msgstr "clik"
|
||||
|
||||
#: main.py:78
|
||||
msgid ""
|
||||
"Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n"
|
||||
"\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example"
|
||||
msgstr ""
|
||||
"Salom, {user_fist_name}!\n"
|
||||
"Bu multilanguage bot misoli.\n"
|
||||
"Mavjud buyruqlar:\n"
|
||||
"\n"
|
||||
"/lang - tilni ozgartirish\n"
|
||||
"/plural - pluralizatsiya misoli"
|
||||
|
||||
#: main.py:102
|
||||
msgid "Language has been changed"
|
||||
msgstr "Til ozgartirildi"
|
||||
|
||||
#: main.py:114
|
||||
msgid "You have {number} click"
|
||||
msgid_plural "You have {number} clicks"
|
||||
msgstr[0] "Sizda {number}ta clik"
|
||||
msgstr[1] "Sizda {number}ta clik"
|
||||
|
||||
#: main.py:120
|
||||
msgid ""
|
||||
"This is clicker.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"Bu clicker.\n"
|
||||
"\n"
|
134
examples/middleware/function_based/i18n_gettext/main.py
Normal file
134
examples/middleware/function_based/i18n_gettext/main.py
Normal file
@ -0,0 +1,134 @@
|
||||
"""
|
||||
In this example you will learn how to adapt your bot to different languages
|
||||
Using built-in class I18N.
|
||||
You need to install babel package 'https://pypi.org/project/Babel/'
|
||||
Babel provides a command-line interface for working with message catalogs
|
||||
After installing babel package you have a script called 'pybabel'
|
||||
Too see all the commands open terminal and type 'pybabel --help'
|
||||
Full description for pybabel commands can be found here: 'https://babel.pocoo.org/en/latest/cmdline.html'
|
||||
Create a directory 'locales' where our translations will be stored
|
||||
First we need to extract texts:
|
||||
pybabel extract -o locales/{domain_name}.pot --input-dirs .
|
||||
{domain_name}.pot - is the file where all translations are saved
|
||||
The name of this file should be the same as domain which you pass to I18N class
|
||||
In this example domain_name will be 'messages'
|
||||
For gettext (singular texts) we use '_' alias and it works perfect
|
||||
You may also you some alias for ngettext (plural texts) but you can face with a problem that
|
||||
your plural texts are not being extracted
|
||||
That is because by default 'pybabel extract' recognizes the following keywords:
|
||||
_, gettext, ngettext, ugettext, ungettext, dgettext, dngettext, N_
|
||||
To add your own keyword you can use '-k' flag
|
||||
In this example for 'ngettext' i will assign double underscore alias '__'
|
||||
Full command with pluralization support will look so:
|
||||
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||
Then create directories with translations (get list of all locales: 'pybabel --list-locales'):
|
||||
pybabel init -i locales/{domain_name}.pot -d locales -l en
|
||||
pybabel init -i locales/{domain_name}.pot -d locales -l ru
|
||||
pybabel init -i locales/{domain_name}.pot -d locales -l uz_Latn
|
||||
Now you can translate the texts located in locales/{language}/LC_MESSAGES/{domain_name}.po
|
||||
After you translated all the texts you need to compile .po files:
|
||||
pybabel compile -d locales
|
||||
When you delete/update your texts you also need to update them in .po files:
|
||||
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||
pybabel update -i locales/{domain_name}.pot -d locales
|
||||
- translate
|
||||
pybabel compile -d locales
|
||||
If you have any exceptions check:
|
||||
- you have installed babel
|
||||
- translations are ready, so you just compiled it
|
||||
- in the commands above you replaced {domain_name} to messages
|
||||
- you are writing commands from correct path in terminal
|
||||
"""
|
||||
|
||||
from telebot import TeleBot, types, custom_filters
|
||||
from telebot import apihelper
|
||||
from telebot.storage.memory_storage import StateMemoryStorage
|
||||
|
||||
import keyboards
|
||||
from i18n_class import I18N
|
||||
|
||||
apihelper.ENABLE_MIDDLEWARE = True
|
||||
storage = StateMemoryStorage()
|
||||
# IMPORTANT! This example works only if polling is non-threaded.
|
||||
bot = TeleBot("", state_storage=storage, threaded=False)
|
||||
|
||||
i18n = I18N(translations_path='locales', domain_name='messages')
|
||||
_ = i18n.gettext # for singular translations
|
||||
__ = i18n.ngettext # for plural translations
|
||||
|
||||
# These are example storages, do not use it in a production development
|
||||
users_lang = {}
|
||||
users_clicks = {}
|
||||
|
||||
|
||||
@bot.middleware_handler(update_types=['message', 'callback_query'])
|
||||
def set_contex_language(bot_instance, message):
|
||||
i18n.context_lang.language = users_lang.get(message.from_user.id, 'en')
|
||||
|
||||
|
||||
@bot.message_handler(commands=['start'])
|
||||
def start_handler(message: types.Message):
|
||||
text = _("Hello, {user_fist_name}!\n"
|
||||
"This is the example of multilanguage bot.\n"
|
||||
"Available commands:\n\n"
|
||||
"/lang - change your language\n"
|
||||
"/plural - pluralization example")
|
||||
|
||||
# remember don't use f string for interpolation, use .format method instead
|
||||
text = text.format(user_fist_name=message.from_user.first_name)
|
||||
bot.send_message(message.from_user.id, text)
|
||||
|
||||
|
||||
@bot.message_handler(commands=['lang'])
|
||||
def change_language_handler(message: types.Message):
|
||||
bot.send_message(message.chat.id, "Choose language\nВыберите язык\nTilni tanlang",
|
||||
reply_markup=keyboards.languages_keyboard())
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=custom_filters.TextFilter(contains=['en', 'ru', 'uz_Latn']))
|
||||
def language_handler(call: types.CallbackQuery):
|
||||
lang = call.data
|
||||
users_lang[call.from_user.id] = lang
|
||||
|
||||
# When you change user's language, pass language explicitly coz it's not changed in context
|
||||
bot.edit_message_text(_("Language has been changed", lang=lang), call.from_user.id, call.message.id)
|
||||
bot.delete_state(call.from_user.id)
|
||||
|
||||
|
||||
@bot.message_handler(commands=['plural'])
|
||||
def pluralization_handler(message: types.Message):
|
||||
if not users_clicks.get(message.from_user.id):
|
||||
users_clicks[message.from_user.id] = 0
|
||||
clicks = users_clicks[message.from_user.id]
|
||||
|
||||
text = __(
|
||||
singular="You have {number} click",
|
||||
plural="You have {number} clicks",
|
||||
n=clicks,
|
||||
)
|
||||
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||
bot.send_message(message.chat.id, text, reply_markup=keyboards.clicker_keyboard(_))
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=None, text=custom_filters.TextFilter(equals='click'))
|
||||
def click_handler(call: types.CallbackQuery):
|
||||
if not users_clicks.get(call.from_user.id):
|
||||
users_clicks[call.from_user.id] = 1
|
||||
else:
|
||||
users_clicks[call.from_user.id] += 1
|
||||
|
||||
clicks = users_clicks[call.from_user.id]
|
||||
|
||||
text = __(
|
||||
singular="You have {number} click",
|
||||
plural="You have {number} clicks",
|
||||
n=clicks
|
||||
)
|
||||
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||
bot.edit_message_text(text, call.from_user.id, call.message.message_id,
|
||||
reply_markup=keyboards.clicker_keyboard(_))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bot.add_custom_filter(custom_filters.TextMatchFilter())
|
||||
bot.infinity_polling()
|
17
examples/multibot/README.MD
Normal file
17
examples/multibot/README.MD
Normal file
@ -0,0 +1,17 @@
|
||||
You probably have seen bots which allow you to send them token of your bot and then handle updates providing some functionality for your bot.
|
||||
<br>
|
||||
This type of bots are called <b>multibots</b>. They are created using webhooks.
|
||||
<br>
|
||||
|
||||
This is the example of simple multibot.<br>
|
||||
In order to reproduce this example you need to have <b>domain and ssl connection</b>.
|
||||
<br>
|
||||
If you have, go to config.py and specify your data.
|
||||
<br>
|
||||
There is also file called <b>nginx_conf.conf</b>, we will use nginx as proxy-server and this file is example nginx.conf file.
|
||||
<br>
|
||||
Make sure that server_name and port are the same in both config and nginx_conf
|
||||
<br>
|
||||
(nginx_conf.conf IS NOT complete, you would probably use tools like certbot to add ssl connection to it)
|
||||
<br>
|
||||
Also, in this example I used dictionary as tokens storage, but in production you should use database so that you can re-set webhooks in case bot restarts.
|
6
examples/multibot/config.py
Normal file
6
examples/multibot/config.py
Normal file
@ -0,0 +1,6 @@
|
||||
MAIN_BOT_TOKEN = "your_main_bot_token"
|
||||
|
||||
WEBHOOK_HOST = "your_domain.com"
|
||||
WEBHOOK_PATH = "telegram_webhook"
|
||||
WEBAPP_HOST = "0.0.0.0"
|
||||
WEBAPP_PORT = 3500
|
14
examples/multibot/handlers.py
Normal file
14
examples/multibot/handlers.py
Normal file
@ -0,0 +1,14 @@
|
||||
from telebot import types, TeleBot
|
||||
|
||||
|
||||
def hello_handler(message: types.Message, bot: TeleBot):
|
||||
bot.send_message(message.chat.id, "Hi :)")
|
||||
|
||||
|
||||
def echo_handler(message: types.Message, bot: TeleBot):
|
||||
bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
def register_handlers(bot: TeleBot):
|
||||
bot.register_message_handler(hello_handler, func=lambda message: message.text == 'Hello', pass_bot=True)
|
||||
bot.register_message_handler(echo_handler, pass_bot=True)
|
48
examples/multibot/main.py
Normal file
48
examples/multibot/main.py
Normal file
@ -0,0 +1,48 @@
|
||||
from flask import Flask
|
||||
from flask import request, abort
|
||||
from telebot import TeleBot, types, util
|
||||
from handlers import register_handlers
|
||||
|
||||
import config
|
||||
|
||||
main_bot = TeleBot(config.MAIN_BOT_TOKEN)
|
||||
app = Flask(__name__)
|
||||
tokens = {config.MAIN_BOT_TOKEN: True}
|
||||
|
||||
|
||||
@app.route(f"/{config.WEBHOOK_PATH}/<token>", methods=['POST'])
|
||||
def webhook(token: str):
|
||||
if not tokens.get(token):
|
||||
return abort(404)
|
||||
|
||||
if request.headers.get('content-type') != 'application/json':
|
||||
return abort(403)
|
||||
|
||||
json_string = request.get_data().decode('utf-8')
|
||||
update = types.Update.de_json(json_string)
|
||||
if token == main_bot.token:
|
||||
main_bot.process_new_updates([update])
|
||||
return ''
|
||||
|
||||
from_update_bot = TeleBot(token)
|
||||
register_handlers(from_update_bot)
|
||||
from_update_bot.process_new_updates([update])
|
||||
return ''
|
||||
|
||||
|
||||
@main_bot.message_handler(commands=['add_bot'])
|
||||
def add_bot(message: types.Message):
|
||||
token = util.extract_arguments(message.text)
|
||||
tokens[token] = True
|
||||
|
||||
new_bot = TeleBot(token)
|
||||
new_bot.delete_webhook()
|
||||
new_bot.set_webhook(f"{config.WEBHOOK_HOST}/{config.WEBHOOK_PATH}/{token}")
|
||||
|
||||
new_bot.send_message(message.chat.id, "Webhook was set.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main_bot.delete_webhook()
|
||||
main_bot.set_webhook(f"{config.WEBHOOK_HOST}/{config.WEBHOOK_PATH}/{config.MAIN_BOT_TOKEN}")
|
||||
app.run(host=config.WEBAPP_HOST, port=config.WEBAPP_PORT)
|
8
examples/multibot/nginx_conf.conf
Normal file
8
examples/multibot/nginx_conf.conf
Normal file
@ -0,0 +1,8 @@
|
||||
server {
|
||||
server_name your_domain.com;
|
||||
|
||||
location /telegram_webhook/ {
|
||||
proxy_pass http://localhost:3500;
|
||||
}
|
||||
|
||||
}
|
@ -38,21 +38,20 @@ def command_pay(message):
|
||||
"Real cards won't work with me, no money will be debited from your account."
|
||||
" Use this test card number to pay for your Time Machine: `4242 4242 4242 4242`"
|
||||
"\n\nThis is your demo invoice:", parse_mode='Markdown')
|
||||
bot.send_invoice(message.chat.id, title='Working Time Machine',
|
||||
description='Want to visit your great-great-great-grandparents?'
|
||||
' Make a fortune at the races?'
|
||||
' Shake hands with Hammurabi and take a stroll in the Hanging Gardens?'
|
||||
' Order our Working Time Machine today!',
|
||||
provider_token=provider_token,
|
||||
currency='usd',
|
||||
bot.send_invoice(
|
||||
message.chat.id, #chat_id
|
||||
'Working Time Machine', #title
|
||||
' Want to visit your great-great-great-grandparents? Make a fortune at the races? Shake hands with Hammurabi and take a stroll in the Hanging Gardens? Order our Working Time Machine today!', #description
|
||||
'HAPPY FRIDAYS COUPON', #invoice_payload
|
||||
provider_token, #provider_token
|
||||
'usd', #currency
|
||||
prices, #prices
|
||||
photo_url='http://erkelzaar.tsudao.com/models/perrotta/TIME_MACHINE.jpg',
|
||||
photo_height=512, # !=0/None or picture won't be shown
|
||||
photo_width=512,
|
||||
photo_size=512,
|
||||
is_flexible=False, # True If you need to set up Shipping Fee
|
||||
prices=prices,
|
||||
start_parameter='time-machine-example',
|
||||
invoice_payload='HAPPY FRIDAYS COUPON')
|
||||
start_parameter='time-machine-example')
|
||||
|
||||
|
||||
@bot.shipping_query_handler(func=lambda query: True)
|
||||
|
63
examples/reply_keyboard_markup_example.py
Normal file
63
examples/reply_keyboard_markup_example.py
Normal file
@ -0,0 +1,63 @@
|
||||
# This example shows you how to create a custom QWERTY keyboard using reply keyboard markup
|
||||
import telebot
|
||||
from telebot.types import ReplyKeyboardMarkup, KeyboardButton
|
||||
|
||||
TOKEN = "<your_token>"
|
||||
bot = telebot.TeleBot(TOKEN)
|
||||
|
||||
keys = ["1","2","3","4","5","6","7","8","9","0","q","w","e","r","t","y","u","i","o","p","a","s","d","f","g","h","j","k","l","z","x","c","v","b","n","m"]
|
||||
symbols = ["1","2","3","4","5","6","7","8","9","0","!","@","#","$","%","^","&","*","(",")","\'","\"","/","\\",",",".",";",":"]
|
||||
|
||||
def keyboard(key_type="Normal"):
|
||||
markup = ReplyKeyboardMarkup(row_width=10)
|
||||
if key_type == "Normal":
|
||||
row = [KeyboardButton(x) for x in keys[:10]]
|
||||
markup.add(*row)
|
||||
row = [KeyboardButton(x) for x in keys[10:20]]
|
||||
markup.add(*row)
|
||||
row = [KeyboardButton(x) for x in keys[20:29]]
|
||||
markup.add(*row)
|
||||
row = [KeyboardButton(x) for x in keys[29:]]
|
||||
markup.add(*row)
|
||||
markup.add(KeyboardButton("Caps Lock"),KeyboardButton("Symbols"),KeyboardButton("🔙Delete"),KeyboardButton("✅Done"))
|
||||
elif key_type == "Symbols":
|
||||
row = [KeyboardButton(x) for x in symbols[:10]]
|
||||
markup.add(*row)
|
||||
row = [KeyboardButton(x) for x in symbols[10:20]]
|
||||
markup.add(*row)
|
||||
row = [KeyboardButton(x) for x in symbols[20:]]
|
||||
markup.add(*row)
|
||||
markup.add(KeyboardButton("Caps Lock"),KeyboardButton("Normal"),KeyboardButton("🔙Delete"),KeyboardButton("✅Done"))
|
||||
else:
|
||||
row = [KeyboardButton(x.upper()) for x in keys[:10]]
|
||||
markup.add(*row)
|
||||
row = [KeyboardButton(x.upper()) for x in keys[10:20]]
|
||||
markup.add(*row)
|
||||
row = [KeyboardButton(x.upper()) for x in keys[20:29]]
|
||||
markup.add(*row)
|
||||
row = [KeyboardButton(x.upper()) for x in keys[29:]]
|
||||
markup.add(*row)
|
||||
markup.add(KeyboardButton("Normal"),KeyboardButton("Symbols"),KeyboardButton("🔙Delete"),KeyboardButton("✅Done"))
|
||||
return markup
|
||||
|
||||
@bot.message_handler(commands=["start"])
|
||||
def start_message(message):
|
||||
bot.send_message(message.chat.id,"You can use the keyboard",reply_markup=keyboard())
|
||||
|
||||
@bot.message_handler(func=lambda message:True)
|
||||
def all_messages(message):
|
||||
if message.text == "✅Done":
|
||||
markup = telebot.types.ReplyKeyboardRemove()
|
||||
bot.send_message(message.from_user.id,"Done with Keyboard",reply_markup=markup)
|
||||
elif message.text == "Symbols":
|
||||
bot.send_message(message.from_user.id,"Special characters",reply_markup=keyboard("Symbols"))
|
||||
elif message.text == "Normal":
|
||||
bot.send_message(message.from_user.id,"Normal Keyboard",reply_markup=keyboard("Normal"))
|
||||
elif message.text == "Caps Lock":
|
||||
bot.send_message(message.from_user.id,"Caps Lock",reply_markup=keyboard("Caps"))
|
||||
elif message.text == "🔙Delete":
|
||||
bot.delete_message(message.from_user.id,message.message_id)
|
||||
else:
|
||||
bot.send_message(message.chat.id,message.text)
|
||||
|
||||
bot.infinity_polling()
|
28
examples/set_command_example.py
Normal file
28
examples/set_command_example.py
Normal file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# This is a set_my_commands example.
|
||||
# Press on [/] button in telegram client.
|
||||
# Important, to update the command menu, be sure to exit the chat with the bot and enter to chat again
|
||||
# Important, command for chat_id and for group have a higher priority than for all
|
||||
|
||||
import telebot
|
||||
|
||||
|
||||
API_TOKEN = '<api_token>'
|
||||
bot = telebot.TeleBot(API_TOKEN)
|
||||
|
||||
# use in for delete with the necessary scope and language_code if necessary
|
||||
bot.delete_my_commands(scope=None, language_code=None)
|
||||
|
||||
bot.set_my_commands(
|
||||
commands=[
|
||||
telebot.types.BotCommand("command1", "command1 description"),
|
||||
telebot.types.BotCommand("command2", "command2 description")
|
||||
],
|
||||
# scope=telebot.types.BotCommandScopeChat(12345678) # use for personal command for users
|
||||
# scope=telebot.types.BotCommandScopeAllPrivateChats() # use for all private chats
|
||||
)
|
||||
|
||||
# check command
|
||||
cmd = bot.get_my_commands(scope=None, language_code=None)
|
||||
print([c.to_json() for c in cmd])
|
42
examples/timer_bot.py
Normal file
42
examples/timer_bot.py
Normal file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# This is a simple bot with schedule timer
|
||||
# https://schedule.readthedocs.io
|
||||
|
||||
import time, threading, schedule
|
||||
from telebot import TeleBot
|
||||
|
||||
API_TOKEN = '<api_token>'
|
||||
bot = TeleBot(API_TOKEN)
|
||||
|
||||
|
||||
@bot.message_handler(commands=['help', 'start'])
|
||||
def send_welcome(message):
|
||||
bot.reply_to(message, "Hi! Use /set <seconds> to set a timer")
|
||||
|
||||
|
||||
def beep(chat_id) -> None:
|
||||
"""Send the beep message."""
|
||||
bot.send_message(chat_id, text='Beep!')
|
||||
|
||||
|
||||
@bot.message_handler(commands=['set'])
|
||||
def set_timer(message):
|
||||
args = message.text.split()
|
||||
if len(args) > 1 and args[1].isdigit():
|
||||
sec = int(args[1])
|
||||
schedule.every(sec).seconds.do(beep, message.chat.id).tag(message.chat.id)
|
||||
else:
|
||||
bot.reply_to(message, 'Usage: /set <seconds>')
|
||||
|
||||
|
||||
@bot.message_handler(commands=['unset'])
|
||||
def unset_timer(message):
|
||||
schedule.clear(message.chat.id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
threading.Thread(target=bot.infinity_polling, name='bot_infinity_polling', daemon=True).start()
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
@ -50,5 +50,12 @@ There are 5 examples in this directory using different libraries:
|
||||
* **Cons:**
|
||||
* Twisted is low-level, which may be good or bad depending on use case
|
||||
* Considerable learning curve - reading docs is a must.
|
||||
* **FastAPI(0.70.1):** *webhook_fastapi_echo_bot.py*
|
||||
* **Pros:**
|
||||
* Can be written for both sync and async
|
||||
* Good documentation
|
||||
* **Cons:**
|
||||
* Requires python 3.6+
|
||||
|
||||
|
||||
*Latest update of this document: 2020-12-17*
|
||||
*Latest update of this document: 01-03-2022
|
||||
|
79
examples/webhook_examples/webhook_fastapi_echo_bot.py
Normal file
79
examples/webhook_examples/webhook_fastapi_echo_bot.py
Normal file
@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This is a simple echo bot using decorators and webhook with fastapi
|
||||
# It echoes any incoming text messages and does not use the polling method.
|
||||
|
||||
import logging
|
||||
import fastapi
|
||||
import telebot
|
||||
|
||||
API_TOKEN = 'TOKEN'
|
||||
|
||||
WEBHOOK_HOST = '<ip/domain>'
|
||||
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)
|
||||
|
||||
app = fastapi.FastAPI()
|
||||
|
||||
|
||||
# Process webhook calls
|
||||
@app.post(f'/{API_TOKEN}/')
|
||||
def process_webhook(update: dict):
|
||||
if update:
|
||||
update = telebot.types.Update.de_json(update)
|
||||
bot.process_new_updates([update])
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
|
||||
# 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'))
|
||||
|
||||
|
||||
import uvicorn
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=WEBHOOK_LISTEN,
|
||||
port=WEBHOOK_PORT,
|
||||
ssl_certfile=WEBHOOK_SSL_CERT,
|
||||
ssl_keyfile=WEBHOOK_SSL_PRIV
|
||||
)
|
@ -5,6 +5,7 @@
|
||||
# Documenation to Tornado: http://tornadoweb.org
|
||||
|
||||
import signal
|
||||
from typing import Optional, Awaitable
|
||||
|
||||
import tornado.httpserver
|
||||
import tornado.ioloop
|
||||
@ -33,12 +34,18 @@ bot = telebot.TeleBot(API_TOKEN)
|
||||
|
||||
|
||||
class Root(tornado.web.RequestHandler):
|
||||
def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
|
||||
pass
|
||||
|
||||
def get(self):
|
||||
self.write("Hi! This is webhook example!")
|
||||
self.finish()
|
||||
|
||||
|
||||
class webhook_serv(tornado.web.RequestHandler):
|
||||
class WebhookServ(tornado.web.RequestHandler):
|
||||
def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
|
||||
pass
|
||||
|
||||
def get(self):
|
||||
self.write("What are you doing here?")
|
||||
self.finish()
|
||||
@ -93,7 +100,7 @@ tornado.options.parse_command_line()
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
application = tornado.web.Application([
|
||||
(r"/", Root),
|
||||
(r"/" + WEBHOOK_SECRET, webhook_serv)
|
||||
(r"/" + WEBHOOK_SECRET, WebhookServ)
|
||||
])
|
||||
|
||||
http_server = tornado.httpserver.HTTPServer(application, ssl_options={
|
||||
|
@ -1,4 +1,4 @@
|
||||
pytest==3.0.2
|
||||
pytest
|
||||
requests==2.20.0
|
||||
wheel==0.24.0
|
||||
aiohttp>=3.8.0,<3.9.0
|
10
setup.py
10
setup.py
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
from setuptools import setup
|
||||
from setuptools import setup, find_packages
|
||||
from io import open
|
||||
import re
|
||||
|
||||
@ -19,19 +19,21 @@ setup(name='pyTelegramBotAPI',
|
||||
author='eternnoir',
|
||||
author_email='eternnoir@gmail.com',
|
||||
url='https://github.com/eternnoir/pyTelegramBotAPI',
|
||||
packages=['telebot'],
|
||||
packages = find_packages(exclude = ['tests', 'examples']),
|
||||
license='GPL2',
|
||||
keywords='telegram bot api tools',
|
||||
install_requires=['requests'],
|
||||
extras_require={
|
||||
'json': 'ujson',
|
||||
'PIL': 'Pillow',
|
||||
'redis': 'redis>=3.4.1'
|
||||
'redis': 'redis>=3.4.1',
|
||||
'aiohttp': 'aiohttp',
|
||||
},
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Environment :: Console',
|
||||
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
|
||||
]
|
||||
],
|
||||
|
||||
)
|
||||
|
1660
telebot/__init__.py
1660
telebot/__init__.py
File diff suppressed because it is too large
Load Diff
@ -47,7 +47,6 @@ CUSTOM_REQUEST_SENDER = None
|
||||
ENABLE_MIDDLEWARE = False
|
||||
|
||||
|
||||
|
||||
def _get_req_session(reset=False):
|
||||
if SESSION_TIME_TO_LIVE:
|
||||
# If session TTL is set - check time passed
|
||||
@ -94,20 +93,14 @@ def _make_request(token, method_name, method='get', params=None, files=None):
|
||||
if 'timeout' in params:
|
||||
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: it's the only function with timeout parameter on the BOT API side
|
||||
# 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 and RETRY_ENGINE == 1:
|
||||
got_result = False
|
||||
@ -134,6 +127,7 @@ def _make_request(token, method_name, method='get', params=None, files=None):
|
||||
timeout=(connect_timeout, read_timeout), proxies=proxy)
|
||||
elif RETRY_ON_ERROR and RETRY_ENGINE == 2:
|
||||
http = _get_req_session()
|
||||
# noinspection PyUnresolvedReferences
|
||||
retry_strategy = requests.packages.urllib3.util.retry.Retry(
|
||||
total=MAX_RETRIES,
|
||||
)
|
||||
@ -144,6 +138,7 @@ def _make_request(token, method_name, method='get', params=None, files=None):
|
||||
method, request_url, params=params, files=files,
|
||||
timeout=(connect_timeout, read_timeout), proxies=proxy)
|
||||
elif CUSTOM_REQUEST_SENDER:
|
||||
# noinspection PyCallingNonCallable
|
||||
result = CUSTOM_REQUEST_SENDER(
|
||||
method, request_url, params=params, files=files,
|
||||
timeout=(connect_timeout, read_timeout), proxies=proxy)
|
||||
@ -232,7 +227,7 @@ 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,
|
||||
entities=None, allow_sending_without_reply=None):
|
||||
entities=None, allow_sending_without_reply=None, protect_content=None):
|
||||
"""
|
||||
Use this method to send text messages. On success, the sent Message is returned.
|
||||
:param token:
|
||||
@ -246,6 +241,7 @@ def send_message(
|
||||
:param timeout:
|
||||
:param entities:
|
||||
:param allow_sending_without_reply:
|
||||
:param protect_content:
|
||||
:return:
|
||||
"""
|
||||
method_url = r'sendMessage'
|
||||
@ -266,11 +262,13 @@ def send_message(
|
||||
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
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def set_webhook(token, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None,
|
||||
drop_pending_updates = None, timeout=None):
|
||||
drop_pending_updates = None, timeout=None, secret_token=None):
|
||||
method_url = r'setWebhook'
|
||||
payload = {
|
||||
'url': url if url else "",
|
||||
@ -288,6 +286,8 @@ def set_webhook(token, url=None, certificate=None, max_connections=None, allowed
|
||||
payload['drop_pending_updates'] = drop_pending_updates
|
||||
if timeout:
|
||||
payload['timeout'] = timeout
|
||||
if secret_token:
|
||||
payload['secret_token'] = secret_token
|
||||
return _make_request(token, method_url, params=payload, files=files)
|
||||
|
||||
|
||||
@ -390,19 +390,21 @@ def get_chat_member(token, chat_id, user_id):
|
||||
|
||||
def forward_message(
|
||||
token, chat_id, from_chat_id, message_id,
|
||||
disable_notification=None, timeout=None):
|
||||
disable_notification=None, timeout=None, protect_content=None):
|
||||
method_url = r'forwardMessage'
|
||||
payload = {'chat_id': chat_id, 'from_chat_id': from_chat_id, 'message_id': message_id}
|
||||
if disable_notification is not None:
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['timeout'] = timeout
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def copy_message(token, chat_id, from_chat_id, message_id, caption=None, parse_mode=None, caption_entities=None,
|
||||
disable_notification=None, reply_to_message_id=None, allow_sending_without_reply=None,
|
||||
reply_markup=None, timeout=None):
|
||||
reply_markup=None, timeout=None, protect_content=None):
|
||||
method_url = r'copyMessage'
|
||||
payload = {'chat_id': chat_id, 'from_chat_id': from_chat_id, 'message_id': message_id}
|
||||
if caption is not None:
|
||||
@ -421,13 +423,15 @@ def copy_message(token, chat_id, from_chat_id, message_id, caption=None, parse_m
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if timeout:
|
||||
payload['timeout'] = timeout
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
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, allow_sending_without_reply=None):
|
||||
reply_markup=None, timeout=None, allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendDice'
|
||||
payload = {'chat_id': chat_id}
|
||||
if emoji:
|
||||
@ -442,6 +446,8 @@ def send_dice(
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -449,7 +455,7 @@ 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,
|
||||
caption_entities=None, allow_sending_without_reply=None):
|
||||
caption_entities=None, allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendPhoto'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -475,13 +481,15 @@ def send_photo(
|
||||
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 protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
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, allow_sending_without_reply=None):
|
||||
timeout=None, allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendMediaGroup'
|
||||
media_json, files = convert_input_media_array(media)
|
||||
payload = {'chat_id': chat_id, 'media': media_json}
|
||||
@ -493,6 +501,8 @@ def send_media_group(
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
return _make_request(
|
||||
token, method_url, params=payload,
|
||||
method='post' if files else 'get',
|
||||
@ -504,7 +514,7 @@ def send_location(
|
||||
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):
|
||||
proximity_alert_radius=None, allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendLocation'
|
||||
payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude}
|
||||
if live_period:
|
||||
@ -525,6 +535,8 @@ def send_location(
|
||||
payload['disable_notification'] = disable_notification
|
||||
if timeout:
|
||||
payload['timeout'] = timeout
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -576,7 +588,7 @@ def send_venue(
|
||||
foursquare_id=None, foursquare_type=None, disable_notification=None,
|
||||
reply_to_message_id=None, reply_markup=None, timeout=None,
|
||||
allow_sending_without_reply=None, google_place_id=None,
|
||||
google_place_type=None):
|
||||
google_place_type=None, protect_content=None):
|
||||
method_url = r'sendVenue'
|
||||
payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude, 'title': title, 'address': address}
|
||||
if foursquare_id:
|
||||
@ -597,13 +609,15 @@ def send_venue(
|
||||
payload['google_place_id'] = google_place_id
|
||||
if google_place_type:
|
||||
payload['google_place_type'] = google_place_type
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
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,
|
||||
allow_sending_without_reply=None):
|
||||
allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendContact'
|
||||
payload = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name}
|
||||
if last_name:
|
||||
@ -620,6 +634,9 @@ def send_contact(
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -633,7 +650,7 @@ def send_chat_action(token, chat_id, action, timeout=None):
|
||||
|
||||
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, caption_entities=None, allow_sending_without_reply=None):
|
||||
thumb=None, width=None, height=None, caption_entities=None, allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendVideo'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -673,13 +690,15 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_to_messa
|
||||
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 protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
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, caption_entities=None,
|
||||
allow_sending_without_reply=None):
|
||||
allow_sending_without_reply=None, protect_content=None, width=None, height=None):
|
||||
method_url = r'sendAnimation'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -713,12 +732,18 @@ def send_animation(
|
||||
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 protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
if width:
|
||||
payload['width'] = width
|
||||
if height:
|
||||
payload['height'] = height
|
||||
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, caption_entities=None,
|
||||
allow_sending_without_reply=None):
|
||||
allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendVoice'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -744,11 +769,13 @@ def send_voice(token, chat_id, voice, caption=None, duration=None, reply_to_mess
|
||||
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 protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
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, allow_sending_without_reply=None):
|
||||
disable_notification=None, timeout=None, thumb=None, allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendVideoNote'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -780,12 +807,14 @@ def send_video_note(token, chat_id, data, duration=None, length=None, reply_to_m
|
||||
payload['thumb'] = thumb
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
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,
|
||||
caption_entities=None, allow_sending_without_reply=None):
|
||||
caption_entities=None, allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendAudio'
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -823,12 +852,15 @@ def send_audio(token, chat_id, audio, caption=None, duration=None, performer=Non
|
||||
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 protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
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, caption_entities=None,
|
||||
allow_sending_without_reply=None, disable_content_type_detection=None, visible_file_name=None):
|
||||
allow_sending_without_reply=None, disable_content_type_detection=None, visible_file_name=None,
|
||||
protect_content = None):
|
||||
method_url = get_method_by_type(data_type)
|
||||
payload = {'chat_id': chat_id}
|
||||
files = None
|
||||
@ -863,6 +895,8 @@ def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_m
|
||||
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 protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
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')
|
||||
@ -933,7 +967,7 @@ 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,
|
||||
is_anonymous=None, can_manage_chat=None, can_manage_voice_chats=None):
|
||||
is_anonymous=None, can_manage_chat=None, can_manage_video_chats=None):
|
||||
method_url = 'promoteChatMember'
|
||||
payload = {'chat_id': chat_id, 'user_id': user_id}
|
||||
if can_change_info is not None:
|
||||
@ -956,8 +990,8 @@ def promote_chat_member(
|
||||
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
|
||||
if can_manage_video_chats is not None:
|
||||
payload['can_manage_video_chats'] = can_manage_video_chats
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
@ -969,6 +1003,18 @@ def set_chat_administrator_custom_title(token, chat_id, user_id, custom_title):
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def ban_chat_sender_chat(token, chat_id, sender_chat_id):
|
||||
method_url = 'banChatSenderChat'
|
||||
payload = {'chat_id': chat_id, 'sender_chat_id': sender_chat_id}
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def unban_chat_sender_chat(token, chat_id, sender_chat_id):
|
||||
method_url = 'unbanChatSenderChat'
|
||||
payload = {'chat_id': chat_id, 'sender_chat_id': sender_chat_id}
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def set_chat_permissions(token, chat_id, permissions):
|
||||
method_url = 'setChatPermissions'
|
||||
payload = {
|
||||
@ -1089,6 +1135,40 @@ def get_my_commands(token, scope=None, language_code=None):
|
||||
payload['language_code'] = language_code
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
def set_chat_menu_button(token, chat_id=None, menu_button=None):
|
||||
method_url = r'setChatMenuButton'
|
||||
payload = {}
|
||||
if chat_id:
|
||||
payload['chat_id'] = chat_id
|
||||
if menu_button:
|
||||
payload['menu_button'] = menu_button.to_json()
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
def get_chat_menu_button(token, chat_id=None):
|
||||
method_url = r'getChatMenuButton'
|
||||
payload = {}
|
||||
if chat_id:
|
||||
payload['chat_id'] = chat_id
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def set_my_default_administrator_rights(token, rights=None, for_channels=None):
|
||||
method_url = r'setMyDefaultAdministratorRights'
|
||||
payload = {}
|
||||
if rights:
|
||||
payload['rights'] = rights.to_json()
|
||||
if for_channels is not None:
|
||||
payload['for_channels'] = for_channels
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def get_my_default_administrator_rights(token, for_channels=None):
|
||||
method_url = r'getMyDefaultAdministratorRights'
|
||||
payload = {}
|
||||
if for_channels:
|
||||
payload['for_channels'] = for_channels
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def set_my_commands(token, commands, scope=None, language_code=None):
|
||||
method_url = r'setMyCommands'
|
||||
@ -1224,7 +1304,7 @@ 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,
|
||||
allow_sending_without_reply=None):
|
||||
allow_sending_without_reply=None, protect_content=None):
|
||||
method_url = r'sendGame'
|
||||
payload = {'chat_id': chat_id, 'game_short_name': game_short_name}
|
||||
if disable_notification is not None:
|
||||
@ -1237,6 +1317,8 @@ def send_game(
|
||||
payload['timeout'] = timeout
|
||||
if allow_sending_without_reply is not None:
|
||||
payload['allow_sending_without_reply'] = allow_sending_without_reply
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -1301,7 +1383,8 @@ def send_invoice(
|
||||
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, allow_sending_without_reply=None, max_tip_amount=None, suggested_tip_amounts=None):
|
||||
timeout=None, allow_sending_without_reply=None, max_tip_amount=None, suggested_tip_amounts=None,
|
||||
protect_content=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)
|
||||
@ -1333,6 +1416,7 @@ def send_invoice(
|
||||
: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.
|
||||
:param protect_content:
|
||||
:return:
|
||||
"""
|
||||
method_url = r'sendInvoice'
|
||||
@ -1379,6 +1463,8 @@ def send_invoice(
|
||||
payload['max_tip_amount'] = max_tip_amount
|
||||
if suggested_tip_amounts is not None:
|
||||
payload['suggested_tip_amounts'] = json.dumps(suggested_tip_amounts)
|
||||
if protect_content is not None:
|
||||
payload['protect_content'] = protect_content
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -1476,11 +1562,16 @@ def upload_sticker_file(token, user_id, png_sticker):
|
||||
|
||||
def create_new_sticker_set(
|
||||
token, user_id, name, title, emojis, png_sticker, tgs_sticker,
|
||||
contains_masks=None, mask_position=None):
|
||||
contains_masks=None, mask_position=None, webm_sticker=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
|
||||
if png_sticker:
|
||||
stype = 'png_sticker'
|
||||
elif webm_sticker:
|
||||
stype = 'webm_sticker'
|
||||
else:
|
||||
stype = 'tgs_sticker'
|
||||
sticker = png_sticker or tgs_sticker or webm_sticker
|
||||
files = None
|
||||
if not util.is_string(sticker):
|
||||
files = {stype: sticker}
|
||||
@ -1490,14 +1581,21 @@ def create_new_sticker_set(
|
||||
payload['contains_masks'] = contains_masks
|
||||
if mask_position:
|
||||
payload['mask_position'] = mask_position.to_json()
|
||||
if webm_sticker:
|
||||
payload['webm_sticker'] = webm_sticker
|
||||
return _make_request(token, method_url, params=payload, files=files, method='post')
|
||||
|
||||
|
||||
def add_sticker_to_set(token, user_id, name, emojis, png_sticker, tgs_sticker, mask_position):
|
||||
def add_sticker_to_set(token, user_id, name, emojis, png_sticker, tgs_sticker, mask_position, webm_sticker):
|
||||
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
|
||||
if png_sticker:
|
||||
stype = 'png_sticker'
|
||||
elif webm_sticker:
|
||||
stype = 'webm_sticker'
|
||||
else:
|
||||
stype = 'tgs_sticker'
|
||||
sticker = png_sticker or tgs_sticker or webm_sticker
|
||||
files = None
|
||||
if not util.is_string(sticker):
|
||||
files = {stype: sticker}
|
||||
@ -1520,6 +1618,51 @@ def delete_sticker_from_set(token, sticker):
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def answer_web_app_query(token, web_app_query_id, result: types.InlineQueryResultBase):
|
||||
method_url = 'answerWebAppQuery'
|
||||
payload = {'web_app_query_id': web_app_query_id, 'result': result.to_json()}
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
def create_invoice_link(token, title, description, payload, provider_token,
|
||||
currency, prices, max_tip_amount=None, suggested_tip_amounts=None, provider_data=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):
|
||||
method_url = r'createInvoiceLink'
|
||||
payload = {'title': title, 'description': description, 'payload': payload, 'provider_token': provider_token,
|
||||
'currency': currency, 'prices': _convert_list_json_serializable(prices)}
|
||||
if max_tip_amount:
|
||||
payload['max_tip_amount'] = max_tip_amount
|
||||
if suggested_tip_amounts:
|
||||
payload['suggested_tip_amounts'] = json.dumps(suggested_tip_amounts)
|
||||
if provider_data:
|
||||
payload['provider_data'] = provider_data
|
||||
if photo_url:
|
||||
payload['photo_url'] = photo_url
|
||||
if photo_size:
|
||||
payload['photo_size'] = photo_size
|
||||
if photo_width:
|
||||
payload['photo_width'] = photo_width
|
||||
if photo_height:
|
||||
payload['photo_height'] = photo_height
|
||||
if need_name is not None:
|
||||
payload['need_name'] = need_name
|
||||
if need_phone_number is not None:
|
||||
payload['need_phone_number'] = need_phone_number
|
||||
if need_email is not None:
|
||||
payload['need_email'] = need_email
|
||||
if need_shipping_address is not None:
|
||||
payload['need_shipping_address'] = need_shipping_address
|
||||
if send_phone_number_to_provider is not None:
|
||||
payload['send_phone_number_to_provider'] = send_phone_number_to_provider
|
||||
if send_email_to_provider is not None:
|
||||
payload['send_email_to_provider'] = send_email_to_provider
|
||||
if is_flexible is not None:
|
||||
payload['is_flexible'] = is_flexible
|
||||
return _make_request(token, method_url, params=payload, method='post')
|
||||
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
def send_poll(
|
||||
token, chat_id,
|
||||
@ -1527,7 +1670,7 @@ def send_poll(
|
||||
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_notification=False, reply_to_message_id=None, allow_sending_without_reply=None,
|
||||
reply_markup=None, timeout=None, explanation_entities=None):
|
||||
reply_markup=None, timeout=None, explanation_entities=None, protect_content=None):
|
||||
method_url = r'sendPoll'
|
||||
payload = {
|
||||
'chat_id': str(chat_id),
|
||||
@ -1569,6 +1712,8 @@ def send_poll(
|
||||
if explanation_entities:
|
||||
payload['explanation_entities'] = json.dumps(
|
||||
types.MessageEntity.to_list_of_dicts(explanation_entities))
|
||||
if protect_content:
|
||||
payload['protect_content'] = protect_content
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
@ -1662,7 +1807,8 @@ class ApiException(Exception):
|
||||
super(ApiException, self).__init__("A request to the Telegram API was unsuccessful. {0}".format(msg))
|
||||
self.function_name = function_name
|
||||
self.result = result
|
||||
|
||||
|
||||
|
||||
class ApiHTTPException(ApiException):
|
||||
"""
|
||||
This class represents an Exception thrown when a call to the
|
||||
@ -1674,7 +1820,8 @@ class ApiHTTPException(ApiException):
|
||||
.format(result.status_code, result.reason, result.text.encode('utf8')),
|
||||
function_name,
|
||||
result)
|
||||
|
||||
|
||||
|
||||
class ApiInvalidJSONException(ApiException):
|
||||
"""
|
||||
This class represents an Exception thrown when a call to the
|
||||
@ -1686,7 +1833,8 @@ class ApiInvalidJSONException(ApiException):
|
||||
.format(result.text.encode('utf8')),
|
||||
function_name,
|
||||
result)
|
||||
|
||||
|
||||
|
||||
class ApiTelegramException(ApiException):
|
||||
"""
|
||||
This class represents an Exception thrown when a Telegram API returns error code.
|
||||
@ -1700,4 +1848,3 @@ class ApiTelegramException(ApiException):
|
||||
self.result_json = result_json
|
||||
self.error_code = result_json['error_code']
|
||||
self.description = result_json['description']
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user