Compare commits
231 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
28ed36ffeb | ||
![]() |
601c16622d | ||
![]() |
9e46041d0f | ||
![]() |
f41e17a15c | ||
![]() |
bb1456dda1 | ||
![]() |
756f0bd89a | ||
![]() |
6acc2068de | ||
![]() |
d478e9d8e9 | ||
![]() |
52864ed729 | ||
![]() |
ac09e42f94 | ||
![]() |
afc5389520 | ||
![]() |
30ab7c84b4 | ||
![]() |
9d8f5a97f7 | ||
![]() |
16e944707f | ||
![]() |
9723c70deb | ||
![]() |
6e2ee437ba | ||
![]() |
6789d23c28 | ||
![]() |
0fd8d541f7 | ||
![]() |
65bc807ab7 | ||
![]() |
f6d47f729d | ||
![]() |
62d00cbd5f | ||
![]() |
7bb0951e7f | ||
![]() |
3f978c8d1c | ||
![]() |
c7a52ea9fd | ||
![]() |
a4ae50dbbe | ||
![]() |
d886061981 | ||
![]() |
087891010d | ||
![]() |
aa456d3e8d | ||
![]() |
4b62762c13 | ||
![]() |
fa2f3f9037 | ||
![]() |
b0d4bc6f27 | ||
![]() |
55e99becd0 | ||
![]() |
83db08c93c | ||
![]() |
03fb55bf12 | ||
![]() |
f7a4188a53 | ||
![]() |
bfcf8ca414 | ||
![]() |
8262854acb | ||
![]() |
948f6af924 | ||
![]() |
960dd8be5e | ||
![]() |
afe3f83d32 | ||
![]() |
9f45fb5338 | ||
![]() |
e2e14cfdc1 | ||
![]() |
1c33d602e0 | ||
![]() |
944c5ce002 | ||
![]() |
4063f9f336 | ||
![]() |
b229a2c7e2 | ||
![]() |
74a04c2792 | ||
![]() |
2debd22333 | ||
![]() |
09416e94f5 | ||
![]() |
93d65d87c6 | ||
![]() |
d6b80b8f66 | ||
![]() |
e3d579fa02 | ||
![]() |
27fe37bd6b | ||
![]() |
03437146f1 | ||
![]() |
c58a4b90d5 | ||
![]() |
3196eed2ac | ||
![]() |
883879e390 | ||
![]() |
0d31679280 | ||
![]() |
b2243587a5 | ||
![]() |
e268e5a895 | ||
![]() |
3725e3fff2 | ||
![]() |
2891d1cd8b | ||
![]() |
2909410ce6 | ||
![]() |
d5c003400a | ||
![]() |
15083fed8d | ||
![]() |
80f52d0713 | ||
![]() |
dd916da876 | ||
![]() |
09fc309e38 | ||
![]() |
483aa4165d | ||
![]() |
0455c6d022 | ||
![]() |
a7ae47f2a7 | ||
![]() |
cae7822ce3 | ||
![]() |
f5407d744d | ||
![]() |
059e97a96d | ||
![]() |
b09f8d9cb6 | ||
![]() |
1c4ce35829 | ||
![]() |
e78b0c1150 | ||
![]() |
ff28f5cea5 | ||
![]() |
7e016a0eb2 | ||
![]() |
042daf90c9 | ||
![]() |
5ce03ca50f | ||
![]() |
654d0047da | ||
![]() |
b9fd2881d9 | ||
![]() |
50ed0ac142 | ||
![]() |
512a892bb9 | ||
![]() |
83a4f6ae2e | ||
![]() |
df2d54156b | ||
![]() |
9e3ed843e3 | ||
![]() |
a008d09369 | ||
![]() |
e209d56ce8 | ||
![]() |
5d5b47ea50 | ||
![]() |
14c85ce634 | ||
![]() |
1a9646d607 | ||
![]() |
db54473e0f | ||
![]() |
1c22d2d8d7 | ||
![]() |
f860fb1815 | ||
![]() |
9d5bf0de53 | ||
![]() |
9101a81640 | ||
![]() |
765676b6e1 | ||
![]() |
fd3645fa52 | ||
![]() |
02e06863e7 | ||
![]() |
8efc40730f | ||
![]() |
afdb623358 | ||
![]() |
3b26fda9e7 | ||
![]() |
1779a5607d | ||
![]() |
90997f5adb | ||
![]() |
5ed24b9f42 | ||
![]() |
569e9f6ccb | ||
![]() |
cc9479327b | ||
![]() |
3aa878ff87 | ||
![]() |
ce408591c4 | ||
![]() |
2e03be7829 | ||
![]() |
10e140814d | ||
![]() |
59408aaacd | ||
![]() |
73bcdcc3c3 | ||
![]() |
31d2acc7fa | ||
![]() |
715d516952 | ||
![]() |
64ba75e8cb | ||
![]() |
fc607cee5c | ||
![]() |
6004f9d9af | ||
![]() |
994d96885f | ||
![]() |
773c55f8c0 | ||
![]() |
35148883db | ||
![]() |
88752a01dd | ||
![]() |
bb49f6a702 | ||
![]() |
c5c7468e36 | ||
![]() |
2bba944dc0 | ||
![]() |
23dacbfe8f | ||
![]() |
4be45985a0 | ||
![]() |
1768d9e7ea | ||
![]() |
6f602f417f | ||
![]() |
5cff8da9cd | ||
![]() |
36a0bc0f95 | ||
![]() |
878abc6a0f | ||
![]() |
d4582d9a9d | ||
![]() |
02df39c9fd | ||
![]() |
a504d38418 | ||
![]() |
4c22563974 | ||
![]() |
24710d6b5f | ||
![]() |
2164ee6f2c | ||
![]() |
a3eb985d28 | ||
![]() |
d53a574377 | ||
![]() |
fbd546e59a | ||
![]() |
bd31e21699 | ||
![]() |
767cfe64ee | ||
![]() |
7c3069ccb8 | ||
![]() |
a5a6d5beac | ||
![]() |
0fbfa9bd1e | ||
![]() |
9e21b15781 | ||
![]() |
bd239f6b2f | ||
![]() |
6b3383418e | ||
![]() |
a7a08639cf | ||
![]() |
177603606f | ||
![]() |
96853f4e09 | ||
![]() |
45e28bf9b7 | ||
![]() |
ea5249d1b8 | ||
![]() |
bea77807af | ||
![]() |
880269d9d8 | ||
![]() |
d8b580d81b | ||
![]() |
8bdd8307d5 | ||
![]() |
01bcbbb052 | ||
![]() |
47cd78a349 | ||
![]() |
fd8c87fb78 | ||
![]() |
151a4d9cb7 | ||
![]() |
cf937f8dc2 | ||
![]() |
450e283e50 | ||
![]() |
164a251310 | ||
![]() |
8d5723e062 | ||
![]() |
bc5186ba26 | ||
![]() |
d0f9042fb6 | ||
![]() |
173014fda0 | ||
![]() |
9f0c03fb68 | ||
![]() |
603ae506f2 | ||
![]() |
68502b7756 | ||
![]() |
363391b575 | ||
![]() |
59b73c33dc | ||
![]() |
067fbc2736 | ||
![]() |
4aced9af91 | ||
![]() |
facdbbc2fe | ||
![]() |
9ce03e048e | ||
![]() |
47d9f510a9 | ||
![]() |
46ba3a57aa | ||
![]() |
971fd178f2 | ||
![]() |
d83ff39067 | ||
![]() |
2786af259a | ||
![]() |
1a1d382243 | ||
![]() |
74ae7c5d14 | ||
![]() |
2c6ef7bed9 | ||
![]() |
9d6fccd204 | ||
![]() |
c686d8d2d6 | ||
![]() |
fcba54ccf5 | ||
![]() |
b38834b265 | ||
![]() |
dcee7a98df | ||
![]() |
645357995b | ||
![]() |
ba0c2752a1 | ||
![]() |
b2cc2a4827 | ||
![]() |
5a2e950839 | ||
![]() |
6c98c988ca | ||
![]() |
c6266cfdf2 | ||
![]() |
de68f0d002 | ||
![]() |
bef0e183b4 | ||
![]() |
04c7711b74 | ||
![]() |
3bdae028c3 | ||
![]() |
942862f171 | ||
![]() |
e95f21d413 | ||
![]() |
1aeec0c9d8 | ||
![]() |
5fcb5b8900 | ||
![]() |
f0237ecb0b | ||
![]() |
4790a21f60 | ||
![]() |
ea8d251142 | ||
![]() |
2e61640f5a | ||
![]() |
188b58d8e2 | ||
![]() |
0f84b67b49 | ||
![]() |
8013c8c8e4 | ||
![]() |
11f8004c55 | ||
![]() |
0487838942 | ||
![]() |
ddea5ba06c | ||
![]() |
5c7ced1549 | ||
![]() |
118b24df8f | ||
![]() |
0d8081be35 | ||
![]() |
c59fc4ebc1 | ||
![]() |
64538aa17f | ||
![]() |
7e3bc13f14 | ||
![]() |
e4ec20a5c4 | ||
![]() |
cbc3b586a7 | ||
![]() |
5cf69e3ea9 | ||
![]() |
04a3efd3fb | ||
![]() |
29d7118833 | ||
![]() |
7c6abd5558 | ||
![]() |
d9d37f4a5f | ||
![]() |
71e905b0be |
@ -2,3 +2,4 @@
|
||||
venv
|
||||
config.json
|
||||
*.yaml
|
||||
docs/
|
||||
|
2
.flake8
@ -1,3 +1,3 @@
|
||||
[flake8]
|
||||
exclude = .git,__pycache__,venv
|
||||
exclude = .git,__pycache__,venv,.venv
|
||||
max-line-length = 120
|
8
.github/workflows/lint.yaml
vendored
@ -4,6 +4,8 @@ on: push
|
||||
|
||||
env:
|
||||
PYTHONUNBUFFERED: 1
|
||||
POETRY_VERSION: 1.1.2
|
||||
POETRY_VIRTUALENVS_CREATE: "false"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@ -17,7 +19,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install flake8
|
||||
pip install poetry
|
||||
poetry install
|
||||
- name: Check flake8
|
||||
run: python -m flake8 .
|
||||
run: flake8 .
|
||||
|
7
.gitignore
vendored
@ -6,3 +6,10 @@ __pycache__
|
||||
*.pyc
|
||||
config.json
|
||||
docker-compose-release.yaml
|
||||
docs/build
|
||||
ad.md
|
||||
release.env
|
||||
test.py
|
||||
backup
|
||||
*.mo
|
||||
*.pot
|
||||
|
21
Dockerfile
@ -1,11 +1,26 @@
|
||||
FROM python:3.8-buster
|
||||
|
||||
COPY . /app
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
POETRY_VERSION=1.1.12 \
|
||||
POETRY_VIRTUALENVS_CREATE="false"
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y gettext build-essential && \
|
||||
apt-get clean && rm -rf /var/cache/apt/* && rm -rf /var/lib/apt/lists/* && rm -rf /tmp/*
|
||||
|
||||
|
||||
RUN pip install "poetry==$POETRY_VERSION"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install -r requirements.txt
|
||||
COPY pyproject.toml poetry.lock docker-entrypoint.sh ./
|
||||
RUN poetry install --no-interaction --no-ansi --no-dev
|
||||
|
||||
COPY . /app
|
||||
|
||||
RUN msgfmt locales/zh/LC_MESSAGES/olgram.po -o locales/zh/LC_MESSAGES/olgram.mo --use-fuzzy
|
||||
RUN msgfmt locales/uk/LC_MESSAGES/olgram.po -o locales/uk/LC_MESSAGES/olgram.mo --use-fuzzy
|
||||
RUN msgfmt locales/en/LC_MESSAGES/olgram.po -o locales/en/LC_MESSAGES/olgram.mo --use-fuzzy
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
|
46
README.md
@ -1,44 +1,26 @@
|
||||
# OLGram
|
||||
|
||||
[@olgram](https://t.me/olgrambot) - конструктор ботов обратной связи в Telegram
|
||||
|
||||
[](https://github.com/civsocit/olgram/actions?workflow=Linter)
|
||||
[](https://github.com/civsocit/olgram/actions?workflow=Deploy)
|
||||
[](https://olgram.readthedocs.io)
|
||||
|
||||

|
||||
[@OlgramBot](https://t.me/olgrambot) - конструктор ботов обратной связи в Telegram
|
||||
|
||||
## Возможности и преимущества Olgram Bot
|
||||
|
||||
* **Общение с клиентами**. После подключения бота, вы сможете общаться с вашими пользователями бота через диалог с
|
||||
ботом, либо подключенный отдельно чат, где может находиться ваш колл-центр.
|
||||
* **Все типы сообщений**. Livegram боты поддерживают все типы сообщений — текст, фото, видео, голосовые сообщения и
|
||||
стикеры.
|
||||
* **Open-source**. В отличие от известного проекта Livegram код нашего конструктора полностью открыт.
|
||||
* **Self-hosted**. Вы можете развернуть свой собственный конструктор, если не доверяете нашему.
|
||||
* **Безопасность**. В отличие от Livegram, мы не храним сообщения, которые вы отправляете в бот. А наши сервера
|
||||
располагаются в Германии, что делает проект неподконтрольным российским властям.
|
||||
Документация: https://olgram.readthedocs.io
|
||||
|
||||
|
||||
По любым вопросам, связанным с Olgram, пишите в наш бот обратной связи
|
||||
[@civsocit_feedback_bot](https://t.me/civsocit_feedback_bot)
|
||||
**Olgram** [@OlgramBot](https://t.me/olgrambot) это конструктор, который позволяет создавать боты обратной связи
|
||||
в Telegram. После подключения к Olgram пользователи вашего бота смогут писать сообщения, которые будут
|
||||
пересылаться вам в чат, где вы сможете на них ответить.
|
||||
|
||||
### Для разработчиков: сборка и запуск проекта
|
||||
Такие боты могут вам пригодиться, например:
|
||||
|
||||
Вам потребуется собственный VPS или любой хост со статическим адресом или доменом.
|
||||
* Создайте файл .env и заполните его по образцу example.env. Вам нужно заполнить переменные:
|
||||
* BOT_TOKEN - токен нового бота, получить у [@botfather](https://t.me/botfather)
|
||||
* POSTGRES_PASSWORD - любой случайный пароль
|
||||
* WEBHOOK_HOST - IP адрес или доменное имя сервера, на котором запускается проект
|
||||
* Сохраните файл docker-compose.yaml и соберите его:
|
||||
```
|
||||
sudo docker-compose up -d
|
||||
```
|
||||
*Пример 1.* Вы администрируете Telegram-канал и хотите дать своим подписчикам возможность связаться с вами,
|
||||
но не хотите оставлять свои личные контакты. Тогда вы можете создать бота обратной связи: подписчики будут писать
|
||||
боту, вы будете отвечать через бота анонимно.
|
||||
|
||||
В docker-compose.yaml минимальная конфигурация. Для использования в серьёзных проектах мы советуем:
|
||||
* Приобрести домен и настроить его на свой хост
|
||||
* Наладить реверс-прокси и автоматическое обновление сертификатов - например, с помощью
|
||||
[Traefik](https://github.com/traefik/traefik)
|
||||
* Скрыть IP сервера с помощью [Cloudflire](https://www.cloudflare.com), чтобы пользователи ботов не могли найти IP адрес
|
||||
хоста по Webhook бота.
|
||||
*Пример 2.* Вы организуете небольшой call-центр в Telegram или группу технической поддержки. С помощью бота обратной
|
||||
связи вы можете принимать заявки от пользователей в общий чат ваших специалистов, обсуждать эти заявки и отвечать
|
||||
пользователям прямо из этого чата.
|
||||
|
||||
Пример более сложной конфигурации есть в файле docker-compose-full.yaml
|
||||
Читайте больше: https://olgram.readthedocs.io
|
||||
|
@ -1,7 +1,9 @@
|
||||
# Конфигурация, удобная для разработки в PyCharm: бот запускается без docker, порты postgres и redis открыты на localhost
|
||||
# Не используйте её в production!
|
||||
version: '3'
|
||||
services:
|
||||
postgres:
|
||||
image: kartoza/postgis
|
||||
image: postgres:14
|
||||
environment:
|
||||
- POSTGRES_USER=test_user
|
||||
- POSTGRES_PASSWORD=test_passwd
|
||||
@ -11,7 +13,7 @@ services:
|
||||
volumes:
|
||||
- database:/var/lib/postgresql/data
|
||||
redis:
|
||||
image: 'bitnami/redis:latest'
|
||||
image: 'bitnami/redis:6.2.7'
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
volumes:
|
||||
|
@ -1,7 +1,8 @@
|
||||
# Пример сложной конфигурации сервера: реверс-прокси, автоматическое обновление github
|
||||
version: '3'
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
image: postgres:13.4
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- release.env
|
||||
@ -9,8 +10,10 @@ services:
|
||||
- database:/var/lib/postgresql/data
|
||||
networks:
|
||||
- traefik
|
||||
labels:
|
||||
- 'com.centurylinklabs.watchtower.enable="false"'
|
||||
redis:
|
||||
image: 'bitnami/redis:latest'
|
||||
image: 'bitnami/redis:6.2.7'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
@ -20,6 +23,8 @@ services:
|
||||
- release.env
|
||||
networks:
|
||||
- traefik
|
||||
labels:
|
||||
- 'com.centurylinklabs.watchtower.enable="false"'
|
||||
olgram:
|
||||
image: ghcr.io/civsocit/olgram/bot:stable
|
||||
restart: unless-stopped
|
||||
@ -71,6 +76,8 @@ services:
|
||||
- --certificatesresolvers.le.acme.tlschallenge=false
|
||||
- --certificatesresolvers.le.acme.httpchallenge=true
|
||||
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
|
||||
labels:
|
||||
- 'com.centurylinklabs.watchtower.enable="false"'
|
||||
|
||||
volumes:
|
||||
database:
|
||||
|
36
docker-compose-src.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
# Минимальная конфигурация сервера, код собирается из текущей директории
|
||||
version: '3'
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:13.4
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- database:/var/lib/postgresql/data
|
||||
redis:
|
||||
image: 'bitnami/redis:latest'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
volumes:
|
||||
- redis-db:/bitnami/redis/data
|
||||
env_file:
|
||||
- .env
|
||||
olgram:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- olgram-cert:/cert
|
||||
ports:
|
||||
- "${WEBHOOK_PORT}:80"
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
|
||||
volumes:
|
||||
database:
|
||||
redis-db:
|
||||
olgram-cert:
|
@ -1,3 +1,4 @@
|
||||
# Минимальная конфигурация сервера
|
||||
version: '3'
|
||||
services:
|
||||
postgres:
|
||||
@ -8,7 +9,7 @@ services:
|
||||
volumes:
|
||||
- database:/var/lib/postgresql/data
|
||||
redis:
|
||||
image: 'bitnami/redis:latest'
|
||||
image: 'bitnami/redis:6.2.7'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
|
@ -5,10 +5,11 @@ if [ ! -z "${CUSTOM_CERT}" ]; then
|
||||
echo "Use custom certificate"
|
||||
if [ ! -f /cert/private.key ]; then
|
||||
echo "Generate new certificate"
|
||||
openssl req -newkey rsa:2048 -sha256 -nodes -keyout /cert/private.key -x509 -days 1000 -out /cert/public.pem -subj "/C=US/ST=Berlin/L=Berlin/O=my_org/CN=${WEBHOOK_HOST}"
|
||||
openssl req -newkey rsa:2048 -sha256 -nodes -keyout /cert/private.key -x509 -days 10000 -out /cert/public.pem -subj "/C=US/ST=Berlin/L=Berlin/O=my_org/CN=${WEBHOOK_HOST}"
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
aerich upgrade
|
||||
python main.py
|
||||
python migrate.py
|
||||
python main.py $@
|
||||
|
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/images/addbot.jpg
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
docs/images/added.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
docs/images/ban1.png
Normal file
After Width: | Height: | Size: 297 KiB |
BIN
docs/images/ban2.png
Normal file
After Width: | Height: | Size: 266 KiB |
BIN
docs/images/botfather.jpg
Normal file
After Width: | Height: | Size: 152 KiB |
BIN
docs/images/botfathernew.png
Normal file
After Width: | Height: | Size: 560 KiB |
BIN
docs/images/botfathertoken.png
Normal file
After Width: | Height: | Size: 636 KiB |
BIN
docs/images/chat1.jpg
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
docs/images/chat2.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
docs/images/inline.gif
Normal file
After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
BIN
docs/images/logo1_big.png
Normal file
After Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
BIN
docs/images/settemplates.jpg
Normal file
After Width: | Height: | Size: 175 KiB |
BIN
docs/images/start.jpg
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
docs/images/test.jpg
Normal file
After Width: | Height: | Size: 126 KiB |
BIN
docs/images/test2.jpg
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
docs/images/text1.jpg
Normal file
After Width: | Height: | Size: 197 KiB |
BIN
docs/images/text2.jpg
Normal file
After Width: | Height: | Size: 196 KiB |
BIN
docs/images/text3.jpg
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
docs/images/thread.gif
Normal file
After Width: | Height: | Size: 2.9 MiB |
BIN
docs/images/user_info.jpg
Normal file
After Width: | Height: | Size: 64 KiB |
35
docs/make.bat
Normal file
@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
41
docs/source/about.rst
Normal file
@ -0,0 +1,41 @@
|
||||
О проекте
|
||||
===================================
|
||||
|
||||
|
||||
Зачем нужен Olgram
|
||||
------------
|
||||
|
||||
Olgram - это конструктор ботов обратной связи. Такие боты могут вам пригодиться, например:
|
||||
|
||||
*Пример 1.* Вы администрируете Telegram-канал и хотите дать своим подписчикам возможность связаться с вами,
|
||||
но не хотите оставлять свои личные контакты. Тогда вы можете создать бота обратной связи: подписчики будут писать
|
||||
боту, вы будете отвечать через бота анонимно.
|
||||
|
||||
*Пример 2.* Вы организуете небольшой call-центр в Telegram или группу технической поддержки. С помощью бота обратной
|
||||
связи вы можете принимать заявки от пользователей в общий чат ваших специалистов, обсуждать эти заявки и отвечать
|
||||
пользователям прямо из этого чата.
|
||||
|
||||
.. note::
|
||||
|
||||
Olgram - молодой развивающийся проект. Мы готовы расширить функционал бота под ваш сценарий использования, если он
|
||||
не является слишком узкоспециализированным и пригодится другим пользователям. Напишите нам по этому вопросу
|
||||
`@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
|
||||
|
||||
Почему не Livegram?
|
||||
------------
|
||||
|
||||
Наше принципиальное отличие от `Livegram <https://t.me/LivegramBot>`_ это открытость и безопасность.
|
||||
|
||||
* Olgram не хранит сообщения, которые вы пересылаете через него
|
||||
* Код нашего проекта `полностью открыт <https://github.com/civsocit/olgram>`_
|
||||
* Вы можете развернуть Olgram на своём собственном сервере (читайте :doc:`developer`)
|
||||
* Наши сервера находятся в Германии, мы не подконтрольны российскому или белорусскому правительству
|
||||
|
||||
Всё это позволяет вам использовать Olgram (в отличие от Livegram) в политических и других серьёзных проектах.
|
||||
|
||||
Чтобы приступить к созданию своего первого бота, откройте главу :doc:`quick_start`
|
||||
|
||||
.. note::
|
||||
|
||||
Если у вас возникли вопросы по использованию бота, или вы нашли ошибку - напишите
|
||||
нам `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
|
44
docs/source/additional.rst
Normal file
@ -0,0 +1,44 @@
|
||||
Дополнительно
|
||||
=============
|
||||
|
||||
Донаты
|
||||
----------------
|
||||
|
||||
На рекламу проекта, аренду сервера и пиццу
|
||||
|
||||
Bitcoin:
|
||||
``bc1qlq7cm5chc8flr3fy8ewk967aknq3dwmxtwn9hl``
|
||||
|
||||
Litecoin:
|
||||
``LTC1QXAJSVZ0LW44AA5NYTUCH8CP2G8X7A4CDASE4Y7``
|
||||
|
||||
Как убрать "Этот бот создан с помощью ..."
|
||||
----------------
|
||||
Напишите нам на `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
|
||||
|
||||
|
||||
История изменений
|
||||
----------------
|
||||
|
||||
- `2022-08-01` Защита от флуда
|
||||
- `2022-07-23` Автоответчик не пишет сообщение лишний раз
|
||||
- `2022-07-04` Поддержка двух ботов в одном чате
|
||||
- `2022-06-25` Поддержка HTML\Markdown в стартовом сообщении и автоответчике
|
||||
- `2022-06-25` Пересылка отредактированных сообщений
|
||||
- `2022-06-16` User info по возможности отправляется в том же сообщении, что и сообщение пользователя
|
||||
- `2022-05-26` Возможность отвечать на более старые сообщения (1/2 года)
|
||||
- `2022-04-11` Частичная поддержка украинского, английского языка
|
||||
- `2022-04-09` 'Этот бот создан с помощью...' возможность выключать по промо
|
||||
- `2022-03-17` Политика конфиденциальности
|
||||
- `2022-03-17` Дополнительная информация о пользователях (имя пользователя и тд)
|
||||
- `2022-02-19` Статистика использования бота
|
||||
- `2022-02-16` Потоки сообщений
|
||||
- `2022-02-16` Очистка Redis по timeout
|
||||
- `2022-02-12` Шаблоны ответов
|
||||
- `2022-01-27` Настройки логирования
|
||||
- `2022-01-18` Команды /ban и /unban (возможность банить пользователей)
|
||||
- `2021-12-14` Bugfix обработка изменения ID чата
|
||||
- `2021-10-01` Возможность ограничивать права на бота (ADMIN_ID)
|
||||
- `2021-09-26` Шифрование токенов
|
||||
- `2021-09-26` Добавлен автоответчик
|
||||
- `2021-09-24` Initial
|
35
docs/source/conf.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
|
||||
# -- Project information
|
||||
|
||||
project = 'Olgram'
|
||||
copyright = '2023, Civsocit'
|
||||
author = 'civsocit'
|
||||
|
||||
release = '0.1'
|
||||
version = '0.1.0'
|
||||
|
||||
# -- General configuration
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.duration',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.autosummary',
|
||||
'sphinx.ext.intersphinx',
|
||||
]
|
||||
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/3/', None),
|
||||
'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
|
||||
}
|
||||
intersphinx_disabled_domains = ['std']
|
||||
|
||||
templates_path = ['_templates']
|
||||
|
||||
# -- Options for HTML output
|
||||
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# -- Options for EPUB output
|
||||
epub_show_urls = 'footnote'
|
80
docs/source/developer.rst
Normal file
@ -0,0 +1,80 @@
|
||||
Для разработчиков
|
||||
=================
|
||||
|
||||
.. _run:
|
||||
Сборка и запуск
|
||||
---------------
|
||||
Вы можете развернуть Olgram на своём сервере. Вам потребуется собственный VPS или любой хост со статическим адресом
|
||||
или доменом.
|
||||
|
||||
1. Создайте файл .env и заполните его по образцу `example.env <https://github.com/civsocit/olgram/blob/main/example.env>`_
|
||||
Вам нужно заполнить переменные:
|
||||
|
||||
* ``BOT_TOKEN`` - токен нового бота, получить у `@botfather <https://t.me/botfather>`_
|
||||
* ``POSTGRES_PASSWORD`` - любой случайный пароль
|
||||
* ``TOKEN_ENCRYPTION_KEY`` - любой случайный пароль, отличный от POSTGRES_PASSWORD
|
||||
* ``WEBHOOK_HOST`` - IP адрес или доменное имя сервера, на котором запускается проект
|
||||
|
||||
2. Рядом с файлом .env сохраните файл
|
||||
`docker-compose.yaml <https://github.com/civsocit/olgram/blob/main/docker-compose.yaml>`_ и соберите его:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
(bash) $ sudo docker-compose up -d
|
||||
|
||||
Готово, ваш собственный Olgram запущен!
|
||||
|
||||
.. warning::
|
||||
|
||||
Не потеряйте TOKEN_ENCRYPTION_KEY! Его нельзя восстановить. В случае утери TOKEN_ENCRYPTION_KEY вы потеряете
|
||||
токены всех ботов, которые пользователи зарегистрировали в вашем боте.
|
||||
|
||||
Возможно, вы захотите внести изменения в проект и запустить бот с этими изменениями. Тогда:
|
||||
|
||||
1. Склонируйте репозиторий
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
(bash) $ git clone https://github.com/civsocit/olgram
|
||||
|
||||
2. Внесите в код все изменения, которые хотите внести
|
||||
|
||||
3. В каталоге с репозиторием (рядом с файлами .yaml) создайте файл .env и заполните его, как в инструкции выше
|
||||
|
||||
4. Соберите и запустите сервер:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
(bash) $ sudo docker-compose -f docker-compose-src.yaml up -d
|
||||
|
||||
Дополнительно
|
||||
-------------
|
||||
|
||||
В docker-compose.yaml приведена минимальная конфигурация. Для использования в серьёзных проектах мы советуем:
|
||||
|
||||
* Приобрести домен и настроить его на свой хост
|
||||
* Наладить реверс-прокси и автоматическое обновление сертификатов - например, с помощью `Traefik <https://github.com/traefik/traefik>`_
|
||||
* Скрыть IP сервера с помощью `Cloudflare <https://www.cloudflare.com>`_, чтобы пользователи ботов не могли найти IP адрес хоста по Webhook бота.
|
||||
|
||||
Пример более сложной конфигурации есть в файле `docker-compose-full.yaml <https://github.com/civsocit/olgram/blob/main/docker-compose-full.yaml>`_
|
||||
|
||||
|
||||
Как ограничить доступ к своему боту
|
||||
-----------------------------------
|
||||
|
||||
По-умолчанию все пользователи Telegram могут писать в ваш Olgram и регистрировать там своих ботов. Чтобы ограничить
|
||||
доступ к боту, укажите в переменных окружения (файл .env):
|
||||
|
||||
``ADMIN_ID=<идентификатор чата>``
|
||||
|
||||
Идентификатор чата это либо ваш Telegram ID, либо ID группового чата Telegram. Идентификатор можно посмотреть
|
||||
командой /chatid.
|
||||
|
||||
|
||||
Настройка языка
|
||||
---------------
|
||||
|
||||
Язык по-умолчанию - русский. Поддержку другого языка можно добавлять по образцу китайского в папку locales/
|
||||
(китайский - zh). Код языка указать в настройках .env
|
||||
|
||||
``O_LANG=<идентификатор языка>``
|
25
docs/source/index.rst
Normal file
@ -0,0 +1,25 @@
|
||||
Добро пожаловать в документацию Olgram
|
||||
===================================
|
||||
|
||||
**Olgram** `@olgrambot <https://t.me/olgrambot>`_ это конструктор, который позволяет создавать боты обратной связи
|
||||
в Telegram. После подключения к Olgram пользователи вашего бота смогут писать сообщения, которые будут
|
||||
пересылаться вам в чат, где вы сможете на них ответить. Читайте больше о проекте в главе :doc:`about`.
|
||||
|
||||
Откройте главу :doc:`quick_start` чтобы приступить к созданию своего первого бота!
|
||||
|
||||
Оглавление
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
|
||||
about
|
||||
quick_start
|
||||
templates
|
||||
options
|
||||
developer
|
||||
additional
|
||||
|
||||
.. note::
|
||||
|
||||
Если у вас возникли вопросы по использованию бота, или вы нашли ошибку - напишите
|
||||
нам `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
|
51
docs/source/options.rst
Normal file
@ -0,0 +1,51 @@
|
||||
Опции
|
||||
=============
|
||||
|
||||
.. _threads:
|
||||
|
||||
Потоки сообщений
|
||||
----------------
|
||||
|
||||
Olgram пересылает сообщения так, чтобы сообщения от одного и того же пользователя оставались в одном и том же
|
||||
потоке сообщений. Тогда по кнопке View Replies можно увидеть диалог с этим пользователем, а все остальные сообщения из
|
||||
чата скрываются:
|
||||
|
||||
.. image:: ../images/thread.gif
|
||||
:width: 300
|
||||
|
||||
**Как настроить потоки сообщений**
|
||||
|
||||
Привяжите вашего feedback бота к групповому чату :doc:`quick_start`. В настройках группового чата откройте историю
|
||||
чата для новых участников чата ("Chat history for new members -> Visible"). Изменение этой настройки превращает чат в
|
||||
`супергруппу <https://telegram.org/blog/supergroups5k>`_: потоки сообщений работают только в таких группах
|
||||
|
||||
Включите потоки в настройках бота Olgram Опции->Потоки сообщений
|
||||
|
||||
.. _user_info:
|
||||
|
||||
Данные пользователя
|
||||
-------------------
|
||||
|
||||
При получении входящего сообщения Olgram может пересылать дополнительную информацию об отправителе. Имя, username и
|
||||
идентификатор пользователя. Например так:
|
||||
|
||||
.. image:: ../images/user_info.jpg
|
||||
:width: 300
|
||||
|
||||
Эта функция может быть полезной, чтобы отличить одного пользователя от другого. Имя и username можно сменить, но
|
||||
идентификатор #id остаётся неизменным для одного и того же аккаунта.
|
||||
|
||||
Включить эту функцию можно в настройках бота Olgram Опции->Данные пользователя
|
||||
|
||||
.. note::
|
||||
|
||||
Включение этой опции меняет текст политики конфиденциальности вашего feedback бота (команда /security_policy)
|
||||
и может отпугнуть некоторых пользователей. Не включайте эту опцию без необходимости.
|
||||
|
||||
.. _antiflood:
|
||||
|
||||
Защита от флуда
|
||||
---------------
|
||||
|
||||
При включении этой опции пользователю запрещается отправлять больше одного сообщения в минуту. Используйте её, если
|
||||
не успеваете обрабатывать входящие сообщения.
|
110
docs/source/quick_start.rst
Normal file
@ -0,0 +1,110 @@
|
||||
Быстрый старт
|
||||
=============
|
||||
|
||||
Как создать бота
|
||||
----------------
|
||||
|
||||
Перейдите по ссылке `@OlgramBot <https://t.me/olgrambot>`_ и нажмите Запустить:
|
||||
|
||||
|
||||
.. image:: ../images/start.jpg
|
||||
:width: 300
|
||||
|
||||
Нажмите ссылку "addbot":
|
||||
|
||||
.. image:: ../images/addbot.jpg
|
||||
:width: 300
|
||||
|
||||
И перейдите по ссылке создания бота:
|
||||
|
||||
.. image:: ../images/botfather.jpg
|
||||
:width: 300
|
||||
|
||||
BotFather - это официальный бот Telegram, создающий другие боты, которые и будут помогать вам управлять каналами.
|
||||
|
||||
Запустите его как и Olgram bot и кликните по ссылке "/newbot":
|
||||
|
||||
.. image:: ../images/botfathernew.png
|
||||
:width: 300
|
||||
|
||||
В диалоге прописываете, как вы хотите что бы назывался бот, и название ссылки, ведущей к этому боту. В итоге вы
|
||||
получаете токен:
|
||||
|
||||
.. image:: ../images/botfathertoken.png
|
||||
:width: 300
|
||||
|
||||
Скопируйте этот токен и отправьте в Olgram:
|
||||
|
||||
.. image:: ../images/added.jpg
|
||||
:width: 300
|
||||
|
||||
Готово! Бот добавлен в Olgram. Теперь для человека,желающего что-то спросить, бот будет выглядеть примерно так:
|
||||
|
||||
.. image:: ../images/test.jpg
|
||||
:width: 300
|
||||
|
||||
Для вас же это будет выглядеть так:
|
||||
|
||||
.. image:: ../images/test2.jpg
|
||||
:width: 300
|
||||
|
||||
|
||||
Как изменить текст приветствия
|
||||
------------------------------
|
||||
|
||||
По-умолчанию ваш бот после запуска отправляет приветственное сообщение:
|
||||
|
||||
Здравствуйте! Напишите свой вопрос, и мы ответим вам в ближайшее время
|
||||
|
||||
Вы можете настроить этот текст. Для этого откройте список ботов командой /mybots и выберите нужного бота:
|
||||
|
||||
.. image:: ../images/text1.jpg
|
||||
:width: 300
|
||||
|
||||
В появившемся меню выберите "Текст"
|
||||
|
||||
.. image:: ../images/text2.jpg
|
||||
:width: 300
|
||||
|
||||
Теперь просто отправьте новый текст приветствия.
|
||||
|
||||
Как привязать бота к групповому чату
|
||||
------------------------------------
|
||||
|
||||
По-умолчанию ваш бот пересылает сообщения от пользователей вам в личные сообщения. Бота можно привязать к групповому
|
||||
чату. Для этого добавьте его в групповой чат. Затем откройте список ботов, как в примере выше, выберите нужного бота
|
||||
и нажмите кнопку "Чат":
|
||||
|
||||
.. image:: ../images/chat1.jpg
|
||||
:width: 300
|
||||
|
||||
Затем выберите в списке тот чат, в который добавили бота
|
||||
|
||||
.. image:: ../images/chat2.jpg
|
||||
:width: 300
|
||||
|
||||
Готово. Теперь сообщения от пользователей будут пересылаться в групповой чат.
|
||||
|
||||
.. note::
|
||||
|
||||
Нужно сначала зарегистрировать своего бота в Olgram, и только потом добавить в групповой чат. Если бот уже
|
||||
добавлен в групповой чат, удалите его оттуда и добавьте заново - тогда Olgram сможет пересылать туда сообщения.
|
||||
|
||||
Как блокировать и разблокировать пользователей
|
||||
------------------------------------
|
||||
|
||||
Вы можете отправлять в бан пользователей feedback бота. Для этого есть команды /ban и /unban.
|
||||
Например так:
|
||||
|
||||
.. image:: ../images/ban2.png
|
||||
:width: 300
|
||||
|
||||
Со стороны пользователя этот диалог будет выглядеть так:
|
||||
|
||||
.. image:: ../images/ban1.png
|
||||
:width: 300
|
||||
|
||||
.. note::
|
||||
|
||||
Если у вас возникли вопросы по использованию бота, или вы нашли ошибку - напишите
|
||||
нам `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
|
32
docs/source/templates.rst
Normal file
@ -0,0 +1,32 @@
|
||||
Шаблоны ответов
|
||||
=============
|
||||
|
||||
Иногда в поддержке приходится отвечать на однотипные вопросы однотипными ответами. Например:
|
||||
|
||||
Q. ``Здравствуйте! Когда будет доставлен мой заказ?``
|
||||
|
||||
A. ``Добрый день. Ваш заказ принят в обработку. Среднее время доставки 2-4 дня. Мы уведомим вас об изменении статуса заказа``
|
||||
|
||||
Чтобы не печатать каждый раз одинаковые тексты, в Olgram можно задать список шаблонных ответов. Тогда диалог с
|
||||
пользователем может выглядеть так:
|
||||
|
||||
.. image:: ../images/inline.gif
|
||||
:width: 300
|
||||
|
||||
Заметьте, чтобы увидеть список вариантов ответов, нужно упомянуть вашего feedback бота и нажать пробел
|
||||
|
||||
Как настроить шаблоны
|
||||
---------------------
|
||||
|
||||
Шаблоны можно задать в меню Olgram бота Текст -> Автоответчик -> Шаблоны ответов.
|
||||
|
||||
.. image:: ../images/settemplates.jpg
|
||||
:width: 300
|
||||
|
||||
Обязательно включите inline mode в вашем feedback боте. Для этого отправьте @BotFather команду ``/setinline``
|
||||
и следуйте инструкциям
|
||||
|
||||
.. note::
|
||||
|
||||
Может пройти несколько минут, прежде чем добавленные в OlgramBot шаблоны появятся в списке вашего feedback бота
|
||||
|
33
example.env
@ -1,12 +1,35 @@
|
||||
BOT_TOKEN=YOUR_BOT_TOKEN_HERE # example: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12
|
||||
# example: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12 (without quotes!)
|
||||
BOT_TOKEN=YOUR_BOT_TOKEN_HERE
|
||||
|
||||
POSTGRES_USER=olgram
|
||||
POSTGRES_PASSWORD=SOME_RANDOM_PASSWORD_HERE # example: x2y0n27ihiez93kmzj82
|
||||
|
||||
# example: x2y0n27ihiez93kmzj82 (without quotes!)
|
||||
POSTGRES_PASSWORD=SOME_RANDOM_PASSWORD_HERE
|
||||
|
||||
POSTGRES_DB=olgram
|
||||
POSTGRES_HOST=postgres
|
||||
|
||||
WEBHOOK_HOST=YOUR_HOST_HERE # example: 11.143.142.140 or my_domain.com
|
||||
WEBHOOK_PORT=8443 # allowed: 80, 443, 8080, 8443
|
||||
CUSTOM_CERT=true # use that if you don't set up your own domain and let's encrypt certificate
|
||||
# example: i7flci0mx4z5patxnl6m (without quotes!)
|
||||
TOKEN_ENCRYPTION_KEY=SOME_RANDOM_PASSWORD_HERE
|
||||
|
||||
# use your user id or group chat id to restrict access to the bot
|
||||
# ADMIN_ID=223453418
|
||||
|
||||
# use your user id or group chat id to give selected users access to the bot's general statistics (/info command)
|
||||
# SUPERVISOR_ID=223453419
|
||||
|
||||
# example: 11.143.142.140 or my_domain.com (without quotes, without 'https://' prefix!)
|
||||
WEBHOOK_HOST=YOUR_HOST_HERE
|
||||
|
||||
# allowed: 80, 443, 8080, 8443
|
||||
WEBHOOK_PORT=8443
|
||||
# use that if you don't set up your own domain and let's encrypt certificate
|
||||
CUSTOM_CERT=true
|
||||
|
||||
REDIS_PATH=redis://redis
|
||||
|
||||
# Set log level, can be CRITICAL, ERROR, WARNING, INFO, DEBUG. By default it set to WARNING.
|
||||
LOGLEVEL=
|
||||
|
||||
# Uncomment this to switch bot language to English
|
||||
# O_LANG=en
|
||||
|
721
locales/en/LC_MESSAGES/olgram.po
Normal file
@ -0,0 +1,721 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2022-09-02 05:02+0400\n"
|
||||
"PO-Revision-Date: 2022-09-02 05:07+0400\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: en_US\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
"X-Generator: Poedit 3.1\n"
|
||||
|
||||
#: olgram/commands/admin.py:21 olgram/commands/info.py:21
|
||||
#: olgram/commands/promo.py:23 olgram/commands/promo.py:39
|
||||
msgid "Недостаточно прав"
|
||||
msgstr "Not enough permissions"
|
||||
|
||||
#: olgram/commands/admin.py:27
|
||||
msgid "Нужно указать имя бота"
|
||||
msgstr "You need to specify the bot's name"
|
||||
|
||||
#: olgram/commands/admin.py:33
|
||||
msgid "Такого бота нет в системе"
|
||||
msgstr "There is no such bot"
|
||||
|
||||
#: olgram/commands/admin.py:39 olgram/commands/admin.py:53
|
||||
msgid "Пропустить"
|
||||
msgstr "Skip"
|
||||
|
||||
#: olgram/commands/admin.py:42
|
||||
msgid ""
|
||||
"Введите текст, который будет отправлен владельцу бота {0}. Напишите "
|
||||
"'Пропустить' чтобы отменить"
|
||||
msgstr ""
|
||||
"Enter the text that will be sent to the owner of the bot {0}. Write 'Skip' "
|
||||
"to cancel"
|
||||
|
||||
#: olgram/commands/admin.py:50
|
||||
msgid "Поддерживается только текст"
|
||||
msgstr "Only text is supported"
|
||||
|
||||
#: olgram/commands/admin.py:55 olgram/commands/admin.py:71
|
||||
msgid "Отменено"
|
||||
msgstr "Cancelled"
|
||||
|
||||
#: olgram/commands/admin.py:61 olgram/commands/admin.py:69
|
||||
msgid "Отправить"
|
||||
msgstr "Send"
|
||||
|
||||
#: olgram/commands/admin.py:62
|
||||
msgid "Отменить"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: olgram/commands/admin.py:81
|
||||
msgid "Отправлено"
|
||||
msgstr "Sent"
|
||||
|
||||
#: olgram/commands/bot_actions.py:22
|
||||
msgid "Бот удалён"
|
||||
msgstr "Bot removed"
|
||||
|
||||
#: olgram/commands/bot_actions.py:38 olgram/commands/bot_actions.py:50
|
||||
msgid "Текст сброшен"
|
||||
msgstr "Text is reset"
|
||||
|
||||
#: olgram/commands/bot_actions.py:64
|
||||
msgid "Выбран личный чат"
|
||||
msgstr "Personal chat selected"
|
||||
|
||||
#: olgram/commands/bot_actions.py:77
|
||||
msgid "Бот вышел из чатов"
|
||||
msgstr "Bot leaved chats"
|
||||
|
||||
#: olgram/commands/bot_actions.py:83
|
||||
msgid "Нельзя привязать бота к этому чату"
|
||||
msgstr "You can't bind a bot to this chat room"
|
||||
|
||||
#: olgram/commands/bot_actions.py:87
|
||||
msgid "Выбран чат {0}"
|
||||
msgstr "Selected chat {0}"
|
||||
|
||||
#: olgram/commands/bots.py:46
|
||||
msgid ""
|
||||
"У вас уже слишком много ботов. Удалите какой-нибудь свой бот из Olgram(/"
|
||||
"mybots -> (Выбрать бота) -> Удалить бот)"
|
||||
msgstr ""
|
||||
"You already have too many bots. Remove any of your bots from Olgram(/mybots -"
|
||||
"> (Select bot) -> Remove bot)"
|
||||
|
||||
#: olgram/commands/bots.py:50
|
||||
msgid ""
|
||||
"\n"
|
||||
" Чтобы подключить бот, вам нужно выполнить три действия:\n"
|
||||
"\n"
|
||||
" 1. Перейдите в бот @BotFather, нажмите START и отправьте команду /"
|
||||
"newbot\n"
|
||||
" 2. Введите название бота, а потом username бота.\n"
|
||||
" 3. После создания бота перешлите ответное сообщение в этот бот или "
|
||||
"скопируйте и пришлите token бота.\n"
|
||||
"\n"
|
||||
" Важно: не подключайте боты, которые используются в других сервисах "
|
||||
"(Manybot, Chatfuel, Livegram и других).\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" To connect the bot, you need to follow three steps:\n"
|
||||
"\n"
|
||||
" 1. Go to bot @BotFather, press START and send command /newbot\n"
|
||||
" 2. Enter the bot's name and then the bot's username.\n"
|
||||
" 3. Once the bot is created, forward a reply message to this bot or copy "
|
||||
"and send the bot's token.\n"
|
||||
"\n"
|
||||
" Important: do not connect bots that are used in other services (Manybot, "
|
||||
"Chatfuel, Livegram and others).\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:70
|
||||
msgid ""
|
||||
"\n"
|
||||
" Это не токен бота.\n"
|
||||
"\n"
|
||||
" Токен выглядит вот так: 123456789:AAAA-"
|
||||
"abc123_AbcdEFghijKLMnopqrstu12\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" This is not a bot token.\n"
|
||||
"\n"
|
||||
" The token looks like this: 123456789:AAAA-"
|
||||
"abc123_AbcdEFghijKLMnopqrstu12\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:77
|
||||
msgid ""
|
||||
"\n"
|
||||
" Не удалось запустить этого бота: неверный токен\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Failed to start this bot: Wrong token\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:82
|
||||
msgid ""
|
||||
"\n"
|
||||
" Не удалось запустить этого бота: непредвиденная ошибка\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Failed to start this bot: unexpected error\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:87
|
||||
msgid ""
|
||||
"\n"
|
||||
" Такой бот уже есть в базе данных\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Such a bot is already in the database\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:122
|
||||
msgid "Бот добавлен! Список ваших ботов: /mybots"
|
||||
msgstr "Bot added! List of your bots: /mybots"
|
||||
|
||||
#: olgram/commands/info.py:34
|
||||
msgid "Количество ботов: {0}\n"
|
||||
msgstr "Number of bots: {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:35
|
||||
msgid "Количество пользователей (у конструктора): {0}\n"
|
||||
msgstr "Number of users (at the constructor): {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:36
|
||||
msgid "Шаблонов ответов: {0}\n"
|
||||
msgstr "Answer templates: {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:37
|
||||
msgid "Входящих сообщений у всех ботов: {0}\n"
|
||||
msgstr "Incoming messages from all bots: {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:38
|
||||
msgid "Исходящих сообщений у всех ботов: {0}\n"
|
||||
msgstr "All bots have outgoing messages: {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:39
|
||||
msgid "Промо-кодов выдано: {0}\n"
|
||||
msgstr "Promo codes issued: {0}\n"
|
||||
|
||||
#: olgram/commands/menu.py:31
|
||||
msgid ""
|
||||
"\n"
|
||||
" У вас нет добавленных ботов.\n"
|
||||
"\n"
|
||||
" Отправьте команду /addbot, чтобы добавить бот.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" You do not have any bots added.\n"
|
||||
"\n"
|
||||
" Send the command /addbot to add a bot.\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:46
|
||||
msgid "Ваши боты"
|
||||
msgstr "Your bots"
|
||||
|
||||
#: olgram/commands/menu.py:67
|
||||
msgid "Личные сообщения"
|
||||
msgstr "Personal messages"
|
||||
|
||||
#: olgram/commands/menu.py:72
|
||||
msgid "❗️ Выйти из всех чатов"
|
||||
msgstr "❗️ Leave all chats"
|
||||
|
||||
#: olgram/commands/menu.py:77 olgram/commands/menu.py:122
|
||||
#: olgram/commands/menu.py:148 olgram/commands/menu.py:184
|
||||
#: olgram/commands/menu.py:247
|
||||
msgid "<< Назад"
|
||||
msgstr "<< Back"
|
||||
|
||||
#: olgram/commands/menu.py:83
|
||||
msgid ""
|
||||
"\n"
|
||||
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
|
||||
"вам в бот.\n"
|
||||
" Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню "
|
||||
"ещё раз и выберите добавленный чат.\n"
|
||||
" Если ваш бот состоял в групповом чате до того, как его добавили в "
|
||||
"Olgram - удалите бота из чата и добавьте\n"
|
||||
" снова.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" This bot is not added to the chats, so all messages will come to you "
|
||||
"in the bot.\n"
|
||||
" To add a chat - add the bot @{0} to the chat, open this menu again "
|
||||
"and select the added chat.\n"
|
||||
" If your bot was in a group chat before you added it to Olgram - "
|
||||
"remove the bot from the chat and add\n"
|
||||
" again.\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:90
|
||||
msgid ""
|
||||
"\n"
|
||||
" В этом разделе вы можете привязать бота @{0} к чату.\n"
|
||||
" Выберите чат, куда бот будет пересылать сообщения.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" In this section you can bind the @{0} bot to a chat room.\n"
|
||||
" Select the chat room where the bot will forward messages.\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:102
|
||||
msgid "Текст"
|
||||
msgstr "Text"
|
||||
|
||||
#: olgram/commands/menu.py:107
|
||||
msgid "Чат"
|
||||
msgstr "Chat"
|
||||
|
||||
#: olgram/commands/menu.py:112
|
||||
msgid "Удалить бот"
|
||||
msgstr "Delete bot"
|
||||
|
||||
#: olgram/commands/menu.py:117
|
||||
msgid "Статистика"
|
||||
msgstr "Statistics"
|
||||
|
||||
#: olgram/commands/menu.py:126
|
||||
msgid "Опции"
|
||||
msgstr "Options"
|
||||
|
||||
#: olgram/commands/menu.py:131
|
||||
msgid ""
|
||||
"\n"
|
||||
" Управление ботом @{0}.\n"
|
||||
"\n"
|
||||
" Если у вас возникли вопросы по настройке бота, то посмотрите нашу "
|
||||
"справку /help или напишите нам\n"
|
||||
" @civsocit_feedback_bot\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Bot management @{0}.\n"
|
||||
"\n"
|
||||
" If you have any questions about configuring the bot, see our help /help "
|
||||
"or email us\n"
|
||||
" @civsocit_feedback_bot\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:143
|
||||
msgid "Да, удалить бот"
|
||||
msgstr "Yes, delete the bot"
|
||||
|
||||
#: olgram/commands/menu.py:152
|
||||
msgid ""
|
||||
"\n"
|
||||
" Вы уверены, что хотите удалить бота @{0}?\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Are you sure you want to delete the bot @{0}?\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:161
|
||||
msgid "Потоки сообщений"
|
||||
msgstr "Message threads"
|
||||
|
||||
#: olgram/commands/menu.py:166
|
||||
msgid "Данные пользователя"
|
||||
msgstr "User data"
|
||||
|
||||
#: olgram/commands/menu.py:171
|
||||
msgid "Антифлуд"
|
||||
msgstr "Antiflood"
|
||||
|
||||
#: olgram/commands/menu.py:178
|
||||
msgid "Olgram подпись"
|
||||
msgstr "Olgram signature"
|
||||
|
||||
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
|
||||
msgid "включены"
|
||||
msgstr "enabled"
|
||||
|
||||
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
|
||||
msgid "выключены"
|
||||
msgstr "disabled"
|
||||
|
||||
#: olgram/commands/menu.py:191
|
||||
#, fuzzy
|
||||
#| msgid "включены"
|
||||
msgid "включен"
|
||||
msgstr "enabled"
|
||||
|
||||
#: olgram/commands/menu.py:191
|
||||
#, fuzzy
|
||||
#| msgid "выключены"
|
||||
msgid "выключен"
|
||||
msgstr "disabled"
|
||||
|
||||
#: olgram/commands/menu.py:192
|
||||
msgid ""
|
||||
"\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#threads\">Потоки сообщений</a>: <b>{0}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
|
||||
"info\">Данные пользователя</a>: <b>{1}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#antiflood\">Антифлуд</a>: <b>{2}</b>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#threads\">Threads</a>: <b>{0}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
|
||||
"info\">User data</a>: <b>{1}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#antiflood\">Antiflood</a>: <b>{2}</b>"
|
||||
|
||||
#: olgram/commands/menu.py:199
|
||||
msgid "включена"
|
||||
msgstr "enabled"
|
||||
|
||||
#: olgram/commands/menu.py:199
|
||||
msgid "выключена"
|
||||
msgstr "disabled"
|
||||
|
||||
#: olgram/commands/menu.py:200
|
||||
msgid "Olgram подпись: <b>{0}</b>"
|
||||
msgstr "Olgram signature: <b>{0}</b>"
|
||||
|
||||
#: olgram/commands/menu.py:210 olgram/commands/menu.py:272
|
||||
#: olgram/commands/menu.py:314
|
||||
msgid "<< Завершить редактирование"
|
||||
msgstr "<< Finish editing"
|
||||
|
||||
#: olgram/commands/menu.py:214
|
||||
msgid "Автоответчик"
|
||||
msgstr "Autoresponder"
|
||||
|
||||
#: olgram/commands/menu.py:219 olgram/commands/menu.py:286
|
||||
msgid "Сбросить текст"
|
||||
msgstr "Reset text"
|
||||
|
||||
#: olgram/commands/menu.py:224
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст, который отправляется после того, как "
|
||||
"пользователь отправит вашему боту @{0}\n"
|
||||
" команду /start\n"
|
||||
"\n"
|
||||
" Текущий текст:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" You are now editing the text that is sent after the user sends your bot "
|
||||
"@{0}\n"
|
||||
" /start command.\n"
|
||||
"\n"
|
||||
" Current text:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Send a message to change the text.\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:251
|
||||
msgid ""
|
||||
"\n"
|
||||
" Статистика по боту @{0}\n"
|
||||
"\n"
|
||||
" Входящих сообщений: <b>{1}</b>\n"
|
||||
" Ответных сообщений: <b>{2}</b>\n"
|
||||
" Шаблоны ответов: <b>{3}</b>\n"
|
||||
" Забанено пользователей: <b>{4}</b>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Statistics @{0}\n"
|
||||
"\n"
|
||||
" Income messages: <b>{1}</b>\n"
|
||||
" Response messages: <b>{2}</b>\n"
|
||||
" Tempaltes: <b>{3}</b>\n"
|
||||
" Banned users: <b>{4}</b>\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:276
|
||||
msgid "Предыдущий текст"
|
||||
msgstr "Previous text"
|
||||
|
||||
#: olgram/commands/menu.py:281
|
||||
msgid "Шаблоны ответов..."
|
||||
msgstr "Answer templates..."
|
||||
|
||||
#: olgram/commands/menu.py:291
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
|
||||
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
|
||||
"отключено.\n"
|
||||
"\n"
|
||||
" Текущий текст:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" You are now editing the autoresponder text. This message is sent in "
|
||||
"response to all incoming @{0} messages automatically. It is disabled by "
|
||||
"default.\n"
|
||||
"\n"
|
||||
" Current text:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Send a message to change the text.\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:301
|
||||
msgid "(отключено)"
|
||||
msgstr "(disabled)"
|
||||
|
||||
#: olgram/commands/menu.py:318
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
|
||||
"\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Отправьте какую-нибудь фразу (например: \"Ваш заказ готов, ожидайте!\"), "
|
||||
"чтобы добавить её в шаблон.\n"
|
||||
" Чтобы удалить шаблон из списка, отправьте его номер в списке (например, "
|
||||
"4)\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" You are currently editing the answer templates for @{0}. Current "
|
||||
"templates:\n"
|
||||
"\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>.\n"
|
||||
" Send some phrase (e.g., \"Your order is ready, wait!\") to add to the "
|
||||
"template.\n"
|
||||
" To remove a template from the list, send its number in the list (for "
|
||||
"example, 4) "
|
||||
|
||||
#: olgram/commands/menu.py:337
|
||||
msgid "(нет шаблонов)"
|
||||
msgstr "(no templates)"
|
||||
|
||||
#: olgram/commands/menu.py:376
|
||||
msgid "У вас нет шаблонов, чтобы их удалять"
|
||||
msgstr "You don't have templates to delete them"
|
||||
|
||||
#: olgram/commands/menu.py:378
|
||||
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
|
||||
msgstr "To delete a template, enter a number between 0 and {0}"
|
||||
|
||||
#: olgram/commands/menu.py:386
|
||||
msgid "У вашего бота уже слишком много шаблонов"
|
||||
msgstr "Your bot already has too many templates"
|
||||
|
||||
#: olgram/commands/menu.py:390
|
||||
msgid "Такой текст уже есть в списке шаблонов"
|
||||
msgstr "This text is already in the list of templates"
|
||||
|
||||
#: olgram/commands/menu.py:408
|
||||
msgid "У вас нет прав на этого бота"
|
||||
msgstr "You have no permissions to this bot"
|
||||
|
||||
#: olgram/commands/promo.py:27
|
||||
msgid ""
|
||||
"Новый промокод\n"
|
||||
"```{0}```"
|
||||
msgstr ""
|
||||
"New promo code\n"
|
||||
"```{0}```"
|
||||
|
||||
#: olgram/commands/promo.py:46
|
||||
msgid "Неправильный токен"
|
||||
msgstr "Incorrect token"
|
||||
|
||||
#: olgram/commands/promo.py:49
|
||||
msgid "Такого кода не существует"
|
||||
msgstr "There is no such code"
|
||||
|
||||
#: olgram/commands/promo.py:59
|
||||
msgid "Промокод отозван"
|
||||
msgstr "Promotion code withdrawn"
|
||||
|
||||
#: olgram/commands/promo.py:70
|
||||
msgid ""
|
||||
"Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>"
|
||||
msgstr ""
|
||||
"Specify the argument: promo code. For example: <pre>/setpromo my-promo-"
|
||||
"code</pre>"
|
||||
|
||||
#: olgram/commands/promo.py:78 olgram/commands/promo.py:82
|
||||
msgid "Промокод не найден"
|
||||
msgstr "Promo code not found"
|
||||
|
||||
#: olgram/commands/promo.py:85
|
||||
msgid "Промокод уже использован"
|
||||
msgstr "Promo code has already been used"
|
||||
|
||||
#: olgram/commands/promo.py:91
|
||||
msgid "Промокод активирован! Спасибо 🙌"
|
||||
msgstr "Promo code activated! Thank you 🙌"
|
||||
|
||||
#: olgram/commands/start.py:23
|
||||
msgid ""
|
||||
"\n"
|
||||
" Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее "
|
||||
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>. Следите за "
|
||||
"обновлениями <a href=\"https://t.me/civsoc_it\">здесь</a>.\n"
|
||||
"\n"
|
||||
" Используйте эти команды, чтобы управлять этим ботом:\n"
|
||||
"\n"
|
||||
" /addbot - добавить бот\n"
|
||||
" /mybots - управление ботами\n"
|
||||
"\n"
|
||||
" /help - помощь\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Olgram Bot is a feedback bot contructor for Telegram. More info <a "
|
||||
"href=\"https://olgram.readthedocs.io\">here</a>.\n"
|
||||
"\n"
|
||||
" Use that commands to control bot:\n"
|
||||
"\n"
|
||||
" /addbot - add bot\n"
|
||||
" /mybots - bot control\n"
|
||||
"\n"
|
||||
" /help - help\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/start.py:43
|
||||
msgid ""
|
||||
"\n"
|
||||
" Читайте инструкции на нашем сайте https://olgram.readthedocs.io\n"
|
||||
" Техническая поддержка: @civsocit_feedback_bot\n"
|
||||
" Версия {0}\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Read the instructions on our website at https://olgram.readthedocs.io\n"
|
||||
" Technical support: @civsocit_feedback_bot\n"
|
||||
" Version {0}\n"
|
||||
" "
|
||||
|
||||
#: olgram/models/models.py:30
|
||||
msgid ""
|
||||
"\n"
|
||||
" Здравствуйте!\n"
|
||||
" Напишите ваш вопрос и мы ответим вам в ближайшее время.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Hello!\n"
|
||||
" Write your question and we will answer you shortly.\n"
|
||||
" "
|
||||
|
||||
#: olgram/utils/permissions.py:40
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу 😞"
|
||||
msgstr "The bot owner has restricted access to this functionality 😞"
|
||||
|
||||
#: olgram/utils/permissions.py:52
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу😞"
|
||||
msgstr "The owner of the bot has restricted access to this function😞"
|
||||
|
||||
#: server/custom.py:55
|
||||
msgid ""
|
||||
"<b>Политика конфиденциальности</b>\n"
|
||||
"\n"
|
||||
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
|
||||
"отправке сообщения (кроме команд /start и /security_policy) ваш "
|
||||
"идентификатор пользователя записывается в кеш на некоторое время и потом "
|
||||
"удаляется из кеша. Этот идентификатор используется только для общения с "
|
||||
"оператором; боты Olgram не делают массовых рассылок.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"<b>Privacy Policy</b>.\n"
|
||||
"\n"
|
||||
"This bot does not store your messages, username and @username. When you send "
|
||||
"a message (except for /start and /security_policy), your username is cached "
|
||||
"for a while and then deleted from the cache. This ID is only used for "
|
||||
"communicating with the operator; Olgram bots do not do mass mailings.\n"
|
||||
"\n"
|
||||
|
||||
#: server/custom.py:61
|
||||
msgid ""
|
||||
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
|
||||
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
|
||||
"силу настроек, которые оператор указал при создании бота."
|
||||
msgstr ""
|
||||
"When sending a message (except /start and /security_policy), the operator "
|
||||
"<b>sees</b> your username, @username and user ID by virtue of the settings "
|
||||
"that the operator specified when creating the bot."
|
||||
|
||||
#: server/custom.py:65
|
||||
msgid ""
|
||||
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
|
||||
"видеть ваш username, имя пользователя и другую информацию."
|
||||
msgstr ""
|
||||
"Depending on your Telegram privacy settings, the operator may see your "
|
||||
"username, username and other information."
|
||||
|
||||
#: server/custom.py:76
|
||||
msgid "Сообщение от пользователя "
|
||||
msgstr "Message from the user "
|
||||
|
||||
#: server/custom.py:135
|
||||
msgid "Вы заблокированы в этом боте"
|
||||
msgstr "You are blocked in this bot"
|
||||
|
||||
#: server/custom.py:141
|
||||
msgid "Слишком много сообщений, подождите одну минуту"
|
||||
msgstr "Too many messages, wait one minute"
|
||||
|
||||
#: server/custom.py:148
|
||||
msgid "Не удаётся связаться с владельцем бота"
|
||||
msgstr "Cannot contact the owner of the bot"
|
||||
|
||||
#: server/custom.py:179
|
||||
msgid ""
|
||||
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
|
||||
"старое?)</i>"
|
||||
msgstr ""
|
||||
"<i>Cannot forward this message: author not found (message too old?)</i>"
|
||||
|
||||
#: server/custom.py:187
|
||||
msgid "Пользователь заблокирован"
|
||||
msgstr "User is blocked"
|
||||
|
||||
#: server/custom.py:192
|
||||
msgid "Пользователь не был забанен"
|
||||
msgstr "The user was not banned"
|
||||
|
||||
#: server/custom.py:195
|
||||
msgid "Пользователь разбанен"
|
||||
msgstr "A user has been unlocked"
|
||||
|
||||
#: server/custom.py:200
|
||||
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
|
||||
msgstr "<i>Cannot forward the message (has the author blocked the bot?)</i>"
|
||||
|
||||
#: server/server.py:41
|
||||
msgid "(Пере)запустить бота"
|
||||
msgstr "(Re)launch the bot"
|
||||
|
||||
#: server/server.py:42
|
||||
msgid "Политика конфиденциальности"
|
||||
msgstr "Privacy Policy"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "\n"
|
||||
#~ "\n"
|
||||
#~ "Этот бот создан с помощью @OlgramBot"
|
||||
#~ msgstr ""
|
||||
#~ "\n"
|
||||
#~ "\n"
|
||||
#~ "This bot was created using @OlgramBot"
|
25
locales/locale.py
Normal file
@ -0,0 +1,25 @@
|
||||
import gettext
|
||||
from olgram.settings import BotSettings
|
||||
from os.path import dirname
|
||||
|
||||
locales_dir = dirname(__file__)
|
||||
|
||||
|
||||
def dummy_translator(x: str) -> str:
|
||||
return x
|
||||
|
||||
|
||||
lang = BotSettings.language()
|
||||
if lang == "ru":
|
||||
_ = dummy_translator
|
||||
else:
|
||||
t = gettext.translation("olgram", localedir=locales_dir, languages=[lang])
|
||||
_ = t.gettext
|
||||
|
||||
|
||||
translators = {
|
||||
"ru": dummy_translator,
|
||||
"uk": gettext.translation("olgram", localedir=locales_dir, languages=["uk"]).gettext,
|
||||
"zh": gettext.translation("olgram", localedir=locales_dir, languages=["zh"]).gettext,
|
||||
"en": gettext.translation("olgram", localedir=locales_dir, languages=["en"]).gettext,
|
||||
}
|
733
locales/uk/LC_MESSAGES/olgram.po
Normal file
@ -0,0 +1,733 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2022-09-02 05:07+0400\n"
|
||||
"PO-Revision-Date: 2022-09-02 05:12+0400\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: uk_UA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
"X-Generator: Poedit 3.1\n"
|
||||
|
||||
#: olgram/commands/admin.py:21 olgram/commands/info.py:21
|
||||
#: olgram/commands/promo.py:23 olgram/commands/promo.py:39
|
||||
msgid "Недостаточно прав"
|
||||
msgstr "Недостатньо прав"
|
||||
|
||||
#: olgram/commands/admin.py:27
|
||||
msgid "Нужно указать имя бота"
|
||||
msgstr "Потрібно вказати ім'я бота"
|
||||
|
||||
#: olgram/commands/admin.py:33
|
||||
msgid "Такого бота нет в системе"
|
||||
msgstr "Такого бота немає в системі"
|
||||
|
||||
#: olgram/commands/admin.py:39 olgram/commands/admin.py:53
|
||||
msgid "Пропустить"
|
||||
msgstr "Пропустити"
|
||||
|
||||
#: olgram/commands/admin.py:42
|
||||
msgid ""
|
||||
"Введите текст, который будет отправлен владельцу бота {0}. Напишите "
|
||||
"'Пропустить' чтобы отменить"
|
||||
msgstr ""
|
||||
"Введіть текст, який буде надіслано власнику бота {0}. Напишіть 'Пропустити', "
|
||||
"щоб скасувати"
|
||||
|
||||
#: olgram/commands/admin.py:50
|
||||
msgid "Поддерживается только текст"
|
||||
msgstr "Підтримується лише текст"
|
||||
|
||||
#: olgram/commands/admin.py:55 olgram/commands/admin.py:71
|
||||
msgid "Отменено"
|
||||
msgstr "Скасовано"
|
||||
|
||||
#: olgram/commands/admin.py:61 olgram/commands/admin.py:69
|
||||
msgid "Отправить"
|
||||
msgstr "Надіслати"
|
||||
|
||||
#: olgram/commands/admin.py:62
|
||||
msgid "Отменить"
|
||||
msgstr "Скасувати"
|
||||
|
||||
#: olgram/commands/admin.py:81
|
||||
msgid "Отправлено"
|
||||
msgstr "Надіслано"
|
||||
|
||||
#: olgram/commands/bot_actions.py:22
|
||||
msgid "Бот удалён"
|
||||
msgstr "Бот видалений"
|
||||
|
||||
#: olgram/commands/bot_actions.py:38 olgram/commands/bot_actions.py:50
|
||||
msgid "Текст сброшен"
|
||||
msgstr "Текст скинутий"
|
||||
|
||||
#: olgram/commands/bot_actions.py:64
|
||||
msgid "Выбран личный чат"
|
||||
msgstr "Вибраний особистий чат"
|
||||
|
||||
#: olgram/commands/bot_actions.py:77
|
||||
msgid "Бот вышел из чатов"
|
||||
msgstr "Бот вийшов із чатів"
|
||||
|
||||
#: olgram/commands/bot_actions.py:83
|
||||
msgid "Нельзя привязать бота к этому чату"
|
||||
msgstr "Не можна прив'язати робота до цього чату"
|
||||
|
||||
#: olgram/commands/bot_actions.py:87
|
||||
msgid "Выбран чат {0}"
|
||||
msgstr "Вибраний чат {0}"
|
||||
|
||||
#: olgram/commands/bots.py:46
|
||||
msgid ""
|
||||
"У вас уже слишком много ботов. Удалите какой-нибудь свой бот из Olgram(/"
|
||||
"mybots -> (Выбрать бота) -> Удалить бот)"
|
||||
msgstr ""
|
||||
"У вас вже надто багато роботів. Видаліть якийсь свій бот з Olgram(/mybots -> "
|
||||
"(Вибрати бота) -> Видалити бот)"
|
||||
|
||||
#: olgram/commands/bots.py:50
|
||||
msgid ""
|
||||
"\n"
|
||||
" Чтобы подключить бот, вам нужно выполнить три действия:\n"
|
||||
"\n"
|
||||
" 1. Перейдите в бот @BotFather, нажмите START и отправьте команду /"
|
||||
"newbot\n"
|
||||
" 2. Введите название бота, а потом username бота.\n"
|
||||
" 3. После создания бота перешлите ответное сообщение в этот бот или "
|
||||
"скопируйте и пришлите token бота.\n"
|
||||
"\n"
|
||||
" Важно: не подключайте боты, которые используются в других сервисах "
|
||||
"(Manybot, Chatfuel, Livegram и других).\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Щоб підключити бот, вам потрібно виконати три дії:\n"
|
||||
"\n"
|
||||
" 1. Перейдіть до бот @BotFather, натисніть START і надішліть команду /"
|
||||
"newbot\n"
|
||||
" 2. Введіть назву бота, а потім username бота.\n"
|
||||
" 3. Після створення бота перешліть повідомлення у цей бот або скопіюйте "
|
||||
"і надішліть token бота.\n"
|
||||
"\n"
|
||||
" Важливо: не підключайте роботи, які використовуються в інших сервісах "
|
||||
"(Manybot, Chatfuel, Livegram та інших).\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:70
|
||||
msgid ""
|
||||
"\n"
|
||||
" Это не токен бота.\n"
|
||||
"\n"
|
||||
" Токен выглядит вот так: 123456789:AAAA-"
|
||||
"abc123_AbcdEFghijKLMnopqrstu12\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Це не токен робота.\n"
|
||||
"\n"
|
||||
" Токен виглядає ось так: 123456789:AAAA-"
|
||||
"abc123_AbcdEFghijKLMnopqrstu12\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:77
|
||||
msgid ""
|
||||
"\n"
|
||||
" Не удалось запустить этого бота: неверный токен\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Не вдалося запустити цього бота: неправильний токен\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:82
|
||||
msgid ""
|
||||
"\n"
|
||||
" Не удалось запустить этого бота: непредвиденная ошибка\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Не вдалося запустити цього бота: непередбачена помилка\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:87
|
||||
msgid ""
|
||||
"\n"
|
||||
" Такой бот уже есть в базе данных\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Такий бот вже є у базі даних\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:122
|
||||
msgid "Бот добавлен! Список ваших ботов: /mybots"
|
||||
msgstr "Бот доданий! Список ваших роботів: /mybots"
|
||||
|
||||
#: olgram/commands/info.py:34
|
||||
msgid "Количество ботов: {0}\n"
|
||||
msgstr "Кількість ботів: {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:35
|
||||
msgid "Количество пользователей (у конструктора): {0}\n"
|
||||
msgstr "Кількість користувачів (у конструктора): {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:36
|
||||
msgid "Шаблонов ответов: {0}\n"
|
||||
msgstr "Шаблонів відповідей: {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:37
|
||||
msgid "Входящих сообщений у всех ботов: {0}\n"
|
||||
msgstr "Вхідних повідомлень у всіх роботів: {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:38
|
||||
msgid "Исходящих сообщений у всех ботов: {0}\n"
|
||||
msgstr "Вихідних повідомлень у всіх роботів: {0}\n"
|
||||
|
||||
#: olgram/commands/info.py:39
|
||||
msgid "Промо-кодов выдано: {0}\n"
|
||||
msgstr "Промо-кодів видано: {0}\n"
|
||||
|
||||
#: olgram/commands/menu.py:31
|
||||
msgid ""
|
||||
"\n"
|
||||
" У вас нет добавленных ботов.\n"
|
||||
"\n"
|
||||
" Отправьте команду /addbot, чтобы добавить бот.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" У вас немає доданих роботів.\n"
|
||||
"\n"
|
||||
" Надішліть команду /addbot, щоб додати бот.\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:46
|
||||
msgid "Ваши боты"
|
||||
msgstr "Ваші боти"
|
||||
|
||||
#: olgram/commands/menu.py:67
|
||||
msgid "Личные сообщения"
|
||||
msgstr "Особисті повідомлення"
|
||||
|
||||
#: olgram/commands/menu.py:72
|
||||
msgid "❗️ Выйти из всех чатов"
|
||||
msgstr "❗️ Вийти зі всіх чатів"
|
||||
|
||||
#: olgram/commands/menu.py:77 olgram/commands/menu.py:122
|
||||
#: olgram/commands/menu.py:148 olgram/commands/menu.py:184
|
||||
#: olgram/commands/menu.py:247
|
||||
msgid "<< Назад"
|
||||
msgstr "<< Назад"
|
||||
|
||||
#: olgram/commands/menu.py:83
|
||||
msgid ""
|
||||
"\n"
|
||||
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
|
||||
"вам в бот.\n"
|
||||
" Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню "
|
||||
"ещё раз и выберите добавленный чат.\n"
|
||||
" Если ваш бот состоял в групповом чате до того, как его добавили в "
|
||||
"Olgram - удалите бота из чата и добавьте\n"
|
||||
" снова.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Цей бот не доданий до чатів, тому всі повідомлення будуть приходити "
|
||||
"вам у бот.\n"
|
||||
" Щоб підключити чат — додайте бот @{0} до чату, відкрийте це меню ще "
|
||||
"раз і виберіть доданий чат.\n"
|
||||
" Якщо ваш бот перебував у груповому чаті до того, як його додали до "
|
||||
"Olgram - видаліть бота з чату та додайте\n"
|
||||
" знову.\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:90
|
||||
msgid ""
|
||||
"\n"
|
||||
" В этом разделе вы можете привязать бота @{0} к чату.\n"
|
||||
" Выберите чат, куда бот будет пересылать сообщения.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" У цьому розділі ви можете прив'язати робота @{0} до чату.\n"
|
||||
" Виберіть чат, куди бот пересилатиме повідомлення.\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:102
|
||||
msgid "Текст"
|
||||
msgstr "Текст"
|
||||
|
||||
#: olgram/commands/menu.py:107
|
||||
msgid "Чат"
|
||||
msgstr "Чат"
|
||||
|
||||
#: olgram/commands/menu.py:112
|
||||
msgid "Удалить бот"
|
||||
msgstr "Видалити бот"
|
||||
|
||||
#: olgram/commands/menu.py:117
|
||||
msgid "Статистика"
|
||||
msgstr "Статистика"
|
||||
|
||||
#: olgram/commands/menu.py:126
|
||||
msgid "Опции"
|
||||
msgstr "Опції"
|
||||
|
||||
#: olgram/commands/menu.py:131
|
||||
msgid ""
|
||||
"\n"
|
||||
" Управление ботом @{0}.\n"
|
||||
"\n"
|
||||
" Если у вас возникли вопросы по настройке бота, то посмотрите нашу "
|
||||
"справку /help или напишите нам\n"
|
||||
" @civsocit_feedback_bot\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Управління роботом @{0}.\n"
|
||||
"\n"
|
||||
" Якщо у вас виникли питання з налаштування бота, подивіться нашу довідку /"
|
||||
"help або напишіть нам\n"
|
||||
" @civsocit_feedback_bot\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:143
|
||||
msgid "Да, удалить бот"
|
||||
msgstr "Так, видалити бот"
|
||||
|
||||
#: olgram/commands/menu.py:152
|
||||
msgid ""
|
||||
"\n"
|
||||
" Вы уверены, что хотите удалить бота @{0}?\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Ви впевнені, що хочете видалити бота @{0}?\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:161
|
||||
msgid "Потоки сообщений"
|
||||
msgstr "Потоки повідомлень"
|
||||
|
||||
#: olgram/commands/menu.py:166
|
||||
msgid "Данные пользователя"
|
||||
msgstr "Дані користувача"
|
||||
|
||||
#: olgram/commands/menu.py:171
|
||||
msgid "Антифлуд"
|
||||
msgstr "Антифлуд"
|
||||
|
||||
#: olgram/commands/menu.py:178
|
||||
msgid "Olgram подпись"
|
||||
msgstr "Olgram підпис"
|
||||
|
||||
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
|
||||
msgid "включены"
|
||||
msgstr "включені"
|
||||
|
||||
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
|
||||
msgid "выключены"
|
||||
msgstr "вимкнені"
|
||||
|
||||
#: olgram/commands/menu.py:191
|
||||
#, fuzzy
|
||||
#| msgid "включены"
|
||||
msgid "включен"
|
||||
msgstr "включені"
|
||||
|
||||
#: olgram/commands/menu.py:191
|
||||
#, fuzzy
|
||||
#| msgid "выключены"
|
||||
msgid "выключен"
|
||||
msgstr "вимкнені"
|
||||
|
||||
#: olgram/commands/menu.py:192
|
||||
msgid ""
|
||||
"\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#threads\">Потоки сообщений</a>: <b>{0}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
|
||||
"info\">Данные пользователя</a>: <b>{1}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#antiflood\">Антифлуд</a>: <b>{2}</b>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#threads\">Потоки повідомлень</a>: <b>{0}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
|
||||
"info\">Дані користувача</a>: <b>{1}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#antiflood\">Anti-flood</a>: <b>{2}</b>"
|
||||
|
||||
#: olgram/commands/menu.py:199
|
||||
msgid "включена"
|
||||
msgstr "включена"
|
||||
|
||||
#: olgram/commands/menu.py:199
|
||||
msgid "выключена"
|
||||
msgstr "вимкнена"
|
||||
|
||||
#: olgram/commands/menu.py:200
|
||||
msgid "Olgram подпись: <b>{0}</b>"
|
||||
msgstr "Olgram підпис: <b>{0}</b>"
|
||||
|
||||
#: olgram/commands/menu.py:210 olgram/commands/menu.py:272
|
||||
#: olgram/commands/menu.py:314
|
||||
msgid "<< Завершить редактирование"
|
||||
msgstr "<< Завершити редагування"
|
||||
|
||||
#: olgram/commands/menu.py:214
|
||||
msgid "Автоответчик"
|
||||
msgstr "Автовідповідач"
|
||||
|
||||
#: olgram/commands/menu.py:219 olgram/commands/menu.py:286
|
||||
msgid "Сбросить текст"
|
||||
msgstr "Скинути текст"
|
||||
|
||||
#: olgram/commands/menu.py:224
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст, который отправляется после того, как "
|
||||
"пользователь отправит вашему боту @{0}\n"
|
||||
" команду /start\n"
|
||||
"\n"
|
||||
" Текущий текст:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Зараз ви редагуєте текст, який надсилається після того, як користувач "
|
||||
"надішле вашому боту @{0}\n"
|
||||
" команду /start\n"
|
||||
"\n"
|
||||
" Поточний текст:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Надішліть повідомлення, щоб змінити текст.\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:251
|
||||
msgid ""
|
||||
"\n"
|
||||
" Статистика по боту @{0}\n"
|
||||
"\n"
|
||||
" Входящих сообщений: <b>{1}</b>\n"
|
||||
" Ответных сообщений: <b>{2}</b>\n"
|
||||
" Шаблоны ответов: <b>{3}</b>\n"
|
||||
" Забанено пользователей: <b>{4}</b>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Статистика з роботи @{0}\n"
|
||||
"\n"
|
||||
" Вхідних повідомлень: <b>{1}</b>\n"
|
||||
" Повідомлень у відповідь: <b>{2}</b>\n"
|
||||
" Шаблони відповідей: <b>{3}</b>\n"
|
||||
" Забанено користувачів: <b>{4}</b>\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:276
|
||||
msgid "Предыдущий текст"
|
||||
msgstr "Попередній текст"
|
||||
|
||||
#: olgram/commands/menu.py:281
|
||||
msgid "Шаблоны ответов..."
|
||||
msgstr "Шаблони відповідей..."
|
||||
|
||||
#: olgram/commands/menu.py:291
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
|
||||
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
|
||||
"отключено.\n"
|
||||
"\n"
|
||||
" Текущий текст:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Зараз ви редагуєте текст автовідповідача. Це повідомлення надсилається у "
|
||||
"відповідь на всі вхідні повідомлення @{0} автоматично. За замовчуванням його "
|
||||
"вимкнено.\n"
|
||||
"\n"
|
||||
" Поточний текст:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Надішліть повідомлення, щоб змінити текст.\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:301
|
||||
msgid "(отключено)"
|
||||
msgstr "(відключено)"
|
||||
|
||||
#: olgram/commands/menu.py:318
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
|
||||
"\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Отправьте какую-нибудь фразу (например: \"Ваш заказ готов, ожидайте!\"), "
|
||||
"чтобы добавить её в шаблон.\n"
|
||||
" Чтобы удалить шаблон из списка, отправьте его номер в списке (например, "
|
||||
"4)\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Зараз ви редагуєте шаблони для @{0}. Поточні шаблони:\n"
|
||||
"\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Надішліть якусь фразу (наприклад: \"Ваше замовлення готове, чекайте!\"), "
|
||||
"щоб додати її до шаблону.\n"
|
||||
" Щоб видалити шаблон зі списку, відправте його у списку (наприклад, 4)\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:337
|
||||
msgid "(нет шаблонов)"
|
||||
msgstr "(Немає шаблонів)"
|
||||
|
||||
#: olgram/commands/menu.py:376
|
||||
msgid "У вас нет шаблонов, чтобы их удалять"
|
||||
msgstr "У вас немає шаблонів, щоб їх видаляти"
|
||||
|
||||
#: olgram/commands/menu.py:378
|
||||
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
|
||||
msgstr "Неправильне число. Щоб видалити шаблон, введіть число від 0 до {0}"
|
||||
|
||||
#: olgram/commands/menu.py:386
|
||||
msgid "У вашего бота уже слишком много шаблонов"
|
||||
msgstr "У вашого бота вже дуже багато шаблонів"
|
||||
|
||||
#: olgram/commands/menu.py:390
|
||||
msgid "Такой текст уже есть в списке шаблонов"
|
||||
msgstr "Такий текст вже є у списку шаблонів"
|
||||
|
||||
#: olgram/commands/menu.py:408
|
||||
msgid "У вас нет прав на этого бота"
|
||||
msgstr "У вас немає прав на цього бота"
|
||||
|
||||
#: olgram/commands/promo.py:27
|
||||
msgid ""
|
||||
"Новый промокод\n"
|
||||
"```{0}```"
|
||||
msgstr ""
|
||||
"Новий промокод\n"
|
||||
"```{0}```"
|
||||
|
||||
#: olgram/commands/promo.py:46
|
||||
msgid "Неправильный токен"
|
||||
msgstr "Неправильний токен"
|
||||
|
||||
#: olgram/commands/promo.py:49
|
||||
msgid "Такого кода не существует"
|
||||
msgstr "Такого коду не існує"
|
||||
|
||||
#: olgram/commands/promo.py:59
|
||||
msgid "Промокод отозван"
|
||||
msgstr "Промокод відкликаний"
|
||||
|
||||
#: olgram/commands/promo.py:70
|
||||
msgid ""
|
||||
"Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>"
|
||||
msgstr ""
|
||||
"Зазначте аргумент: промокод. Наприклад: <pre>/setpromo my-promo-code</pre>"
|
||||
|
||||
#: olgram/commands/promo.py:78 olgram/commands/promo.py:82
|
||||
msgid "Промокод не найден"
|
||||
msgstr "Промокод не знайдено"
|
||||
|
||||
#: olgram/commands/promo.py:85
|
||||
msgid "Промокод уже использован"
|
||||
msgstr "Промокод уже використаний"
|
||||
|
||||
#: olgram/commands/promo.py:91
|
||||
msgid "Промокод активирован! Спасибо 🙌"
|
||||
msgstr "Промокод активовано! Дякую 🙌"
|
||||
|
||||
#: olgram/commands/start.py:23
|
||||
msgid ""
|
||||
"\n"
|
||||
" Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее "
|
||||
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>. Следите за "
|
||||
"обновлениями <a href=\"https://t.me/civsoc_it\">здесь</a>.\n"
|
||||
"\n"
|
||||
" Используйте эти команды, чтобы управлять этим ботом:\n"
|
||||
"\n"
|
||||
" /addbot - добавить бот\n"
|
||||
" /mybots - управление ботами\n"
|
||||
"\n"
|
||||
" /help - помощь\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Olgram Bot - це конструктор роботів зворотного зв'язку в Telegram. "
|
||||
"Докладніше <a href=\"https://olgram.readthedocs.io\">читайте тут</a>. "
|
||||
"Слідкуйте за оновленнями <a href=\"https://t.me/civsoc_it\">тут</a>.\n"
|
||||
"\n"
|
||||
" Використовуйте ці команди, щоб керувати цим ботом:\n"
|
||||
"\n"
|
||||
" /addbot - додати бот\n"
|
||||
" /mybots - керування ботами\n"
|
||||
"\n"
|
||||
" /help - допомога\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/start.py:43
|
||||
msgid ""
|
||||
"\n"
|
||||
" Читайте инструкции на нашем сайте https://olgram.readthedocs.io\n"
|
||||
" Техническая поддержка: @civsocit_feedback_bot\n"
|
||||
" Версия {0}\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Читайте інструкції на нашому сайті https://olgram.readthedocs.io\n"
|
||||
" Технічна підтримка: @civsocit_feedback_bot\n"
|
||||
" Версія {0}\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/models/models.py:30
|
||||
msgid ""
|
||||
"\n"
|
||||
" Здравствуйте!\n"
|
||||
" Напишите ваш вопрос и мы ответим вам в ближайшее время.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Доброго дня!\n"
|
||||
" Напишіть ваше запитання, і ми відповімо вам найближчим часом.\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/utils/permissions.py:40
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу 😞"
|
||||
msgstr "Власник бота обмежив доступ до цього функціоналу 😞"
|
||||
|
||||
#: olgram/utils/permissions.py:52
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу😞"
|
||||
msgstr "Власник бота обмежив доступ до цього функціоналу 😞"
|
||||
|
||||
#: server/custom.py:55
|
||||
msgid ""
|
||||
"<b>Политика конфиденциальности</b>\n"
|
||||
"\n"
|
||||
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
|
||||
"отправке сообщения (кроме команд /start и /security_policy) ваш "
|
||||
"идентификатор пользователя записывается в кеш на некоторое время и потом "
|
||||
"удаляется из кеша. Этот идентификатор используется только для общения с "
|
||||
"оператором; боты Olgram не делают массовых рассылок.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"<b>Політика конфіденційності</b>\n"
|
||||
"\n"
|
||||
"Цей бот не зберігає ваші повідомлення, ім'я користувача та @username. При "
|
||||
"надсиланні повідомлення (крім команд /start та /security_policy) ваш "
|
||||
"ідентифікатор користувача записується в кеш на деякий час і потім "
|
||||
"видаляється з кеша. Цей ідентифікатор використовується лише для спілкування "
|
||||
"з оператором; боти Olgram не роблять масових розсилок.\n"
|
||||
"\n"
|
||||
|
||||
#: server/custom.py:61
|
||||
msgid ""
|
||||
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
|
||||
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
|
||||
"силу настроек, которые оператор указал при создании бота."
|
||||
msgstr ""
|
||||
"При надсиланні повідомлення (крім команд /start та /security_policy) "
|
||||
"оператор <b>бачить</b> ваше ім'я користувача, @username та ідентифікатор "
|
||||
"користувача через налаштування, які оператор вказав при створенні бота."
|
||||
|
||||
#: server/custom.py:65
|
||||
msgid ""
|
||||
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
|
||||
"видеть ваш username, имя пользователя и другую информацию."
|
||||
msgstr ""
|
||||
"Залежно від ваших налаштувань конфіденційності Telegram оператор може бачити "
|
||||
"ваш username, ім'я користувача та іншу інформацію."
|
||||
|
||||
#: server/custom.py:76
|
||||
msgid "Сообщение от пользователя "
|
||||
msgstr "Допис від користувача "
|
||||
|
||||
#: server/custom.py:135
|
||||
msgid "Вы заблокированы в этом боте"
|
||||
msgstr "Ви заблоковані у цьому боті"
|
||||
|
||||
#: server/custom.py:141
|
||||
msgid "Слишком много сообщений, подождите одну минуту"
|
||||
msgstr "Забагато повідомлень, зачекайте одну хвилину"
|
||||
|
||||
#: server/custom.py:148
|
||||
msgid "Не удаётся связаться с владельцем бота"
|
||||
msgstr "Не вдається зв'язатися з власником бота"
|
||||
|
||||
#: server/custom.py:179
|
||||
msgid ""
|
||||
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
|
||||
"старое?)</i>"
|
||||
msgstr ""
|
||||
"<i>Неможливо надіслати повідомлення: автора не знайдено (повідомлення "
|
||||
"занадто старе?)</i>"
|
||||
|
||||
#: server/custom.py:187
|
||||
msgid "Пользователь заблокирован"
|
||||
msgstr "Користувач заблоковано"
|
||||
|
||||
#: server/custom.py:192
|
||||
msgid "Пользователь не был забанен"
|
||||
msgstr "Користувач не був забанений"
|
||||
|
||||
#: server/custom.py:195
|
||||
msgid "Пользователь разбанен"
|
||||
msgstr "Користувач розбанений"
|
||||
|
||||
#: server/custom.py:200
|
||||
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
|
||||
msgstr "<i>Неможливо надіслати повідомлення (автор заблокував робота?)</i>"
|
||||
|
||||
#: server/server.py:41
|
||||
msgid "(Пере)запустить бота"
|
||||
msgstr "(Пере) запустити бота"
|
||||
|
||||
#: server/server.py:42
|
||||
msgid "Политика конфиденциальности"
|
||||
msgstr "Політика конфіденційності"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "\n"
|
||||
#~ "\n"
|
||||
#~ "Этот бот создан с помощью @OlgramBot"
|
||||
#~ msgstr ""
|
||||
#~ "\n"
|
||||
#~ "\n"
|
||||
#~ "Цей бот створено за допомогою @OlgramBot"
|
565
locales/zh/LC_MESSAGES/olgram.po
Normal file
@ -0,0 +1,565 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2022-03-22 04:36+0300\n"
|
||||
"PO-Revision-Date: 2022-03-22 04:55+0300\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
"Last-Translator: \n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"Language: zh_CN\n"
|
||||
|
||||
#: olgram/commands/bot_actions.py:21
|
||||
msgid "Бот удалён"
|
||||
msgstr "移除机器人"
|
||||
|
||||
#: olgram/commands/bot_actions.py:37 olgram/commands/bot_actions.py:49
|
||||
msgid "Текст сброшен"
|
||||
msgstr "重置文本"
|
||||
|
||||
#: olgram/commands/bot_actions.py:63
|
||||
msgid "Выбран личный чат"
|
||||
msgstr "选择了私聊"
|
||||
|
||||
#: olgram/commands/bot_actions.py:68
|
||||
msgid "Нельзя привязать бота к этому чату"
|
||||
msgstr "你不能将机器人链接到这个群聊"
|
||||
|
||||
#: olgram/commands/bot_actions.py:72
|
||||
msgid "Выбран чат {0}"
|
||||
msgstr "聊天选择 {0}"
|
||||
|
||||
#: olgram/commands/bots.py:42
|
||||
msgid "У вас уже слишком много ботов."
|
||||
msgstr "你已经有太多的机器人了。"
|
||||
|
||||
#: olgram/commands/bots.py:45
|
||||
msgid ""
|
||||
"\n"
|
||||
" Чтобы подключить бот, вам нужно выполнить три действия:\n"
|
||||
"\n"
|
||||
" 1. Перейдите в бот @BotFather, нажмите START и отправьте команду /"
|
||||
"newbot\n"
|
||||
" 2. Введите название бота, а потом username бота.\n"
|
||||
" 3. После создания бота перешлите ответное сообщение в этот бот или "
|
||||
"скопируйте и пришлите token бота.\n"
|
||||
"\n"
|
||||
" Важно: не подключайте боты, которые используются в других сервисах "
|
||||
"(Manybot, Chatfuel, Livegram и других).\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 要连接机器人,你需要遵循三个步骤。\n"
|
||||
"\n"
|
||||
" 1. 转到机器人@BotFather,按/START键并发送/newbot\n"
|
||||
" 2. 输入机器人的名字,然后输入机器人的用户名。\n"
|
||||
" 3. 一旦创建了机器人,就向这个机器人转发一条回复信息,或者复制并发送机器人"
|
||||
"的令牌。\n"
|
||||
"\n"
|
||||
" 重要:不要连接用于其他服务的机器人(Manybot、Chatfuel、Livegram和其"
|
||||
"他)。\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:65
|
||||
msgid ""
|
||||
"\n"
|
||||
" Это не токен бота.\n"
|
||||
"\n"
|
||||
" Токен выглядит вот так: 123456789:AAAA-"
|
||||
"abc123_AbcdEFghijKLMnopqrstu12\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 这不是一个机器人令牌。\n"
|
||||
"\n"
|
||||
" 该令牌看起来像这样:123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:72
|
||||
msgid ""
|
||||
"\n"
|
||||
" Не удалось запустить этого бота: неверный токен\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 运行此机器人失败:错误的令牌\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:77
|
||||
msgid ""
|
||||
"\n"
|
||||
" Не удалось запустить этого бота: непредвиденная ошибка\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 该机器人无法启动:意外错误\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:82
|
||||
msgid ""
|
||||
"\n"
|
||||
" Такой бот уже есть в базе данных\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 这个机器人已经在数据库中出现了\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/bots.py:114
|
||||
msgid "Бот добавлен! Список ваших ботов: /mybots"
|
||||
msgstr "机器人已加入! 你的机器人列表:/mybots"
|
||||
|
||||
#: olgram/commands/info.py:21
|
||||
msgid "Недостаточно прав"
|
||||
msgstr "没有足够的权利"
|
||||
|
||||
#: olgram/commands/info.py:32
|
||||
msgid "Количество ботов: {0}\n"
|
||||
msgstr "机器人的数量。{0}\n"
|
||||
|
||||
#: olgram/commands/info.py:33
|
||||
msgid "Количество пользователей (у конструктора): {0}\n"
|
||||
msgstr "用户的数量(在构造器处)。{0}\n"
|
||||
|
||||
#: olgram/commands/info.py:34
|
||||
msgid "Шаблонов ответов: {0}\n"
|
||||
msgstr "回复模板。{0}\n"
|
||||
|
||||
#: olgram/commands/info.py:35
|
||||
msgid "Входящих сообщений у всех ботов: {0}\n"
|
||||
msgstr "所有的机器人都有传入的信息。{0}\n"
|
||||
|
||||
#: olgram/commands/info.py:36
|
||||
msgid "Исходящих сообщений у всех ботов: {0}\n"
|
||||
msgstr "所有的机器人都有外发信息。{0}\n"
|
||||
|
||||
#: olgram/commands/menu.py:31
|
||||
msgid ""
|
||||
"\n"
|
||||
" У вас нет добавленных ботов.\n"
|
||||
"\n"
|
||||
" Отправьте команду /addbot, чтобы добавить бот.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 你没有添加任何机器人。\n"
|
||||
"\n"
|
||||
" 发送命令/addbot来添加一个机器人。\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:46
|
||||
msgid "Ваши боты"
|
||||
msgstr "你的机器人"
|
||||
|
||||
#: olgram/commands/menu.py:67
|
||||
msgid "Личные сообщения"
|
||||
msgstr "个人留言"
|
||||
|
||||
#: olgram/commands/menu.py:72 olgram/commands/menu.py:117
|
||||
#: olgram/commands/menu.py:143 olgram/commands/menu.py:166
|
||||
#: olgram/commands/menu.py:222
|
||||
msgid "<< Назад"
|
||||
msgstr "<< 返回"
|
||||
|
||||
#: olgram/commands/menu.py:78
|
||||
msgid ""
|
||||
"\n"
|
||||
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
|
||||
"вам в бот.\n"
|
||||
" Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню "
|
||||
"ещё раз и выберите добавленный чат.\n"
|
||||
" Если ваш бот состоял в групповом чате до того, как его добавили в "
|
||||
"Olgram - удалите бота из чата и добавьте\n"
|
||||
" снова.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 这个机器人没有被添加到聊天记录中,所以所有的信息都会在机器人中找到"
|
||||
"你。\n"
|
||||
" 要连接群聊--将机器人@{0}添加到群聊中,再次打开此菜单并选择添加的群"
|
||||
"聊。\n"
|
||||
" 如果你的机器人在添加到Olgram之前是在群组中,请将其从群聊中删"
|
||||
"除,然后添加到群组中。\n"
|
||||
" 再次。\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:85
|
||||
msgid ""
|
||||
"\n"
|
||||
" В этом разделе вы можете привязать бота @{0} к чату.\n"
|
||||
" Выберите чат, куда бот будет пересылать сообщения.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 在本节中,您可以将@{0}机器人绑定到一个群聊中。\n"
|
||||
" 选择机器人将转发消息的群聊。\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:97
|
||||
msgid "Текст"
|
||||
msgstr "自动回复"
|
||||
|
||||
#: olgram/commands/menu.py:102
|
||||
msgid "Чат"
|
||||
msgstr "群聊"
|
||||
|
||||
#: olgram/commands/menu.py:107
|
||||
msgid "Удалить бот"
|
||||
msgstr "删除机器人"
|
||||
|
||||
#: olgram/commands/menu.py:112
|
||||
msgid "Статистика"
|
||||
msgstr "统计数据"
|
||||
|
||||
#: olgram/commands/menu.py:121
|
||||
msgid "Опции"
|
||||
msgstr "选择"
|
||||
|
||||
#: olgram/commands/menu.py:126
|
||||
msgid ""
|
||||
"\n"
|
||||
" Управление ботом @{0}.\n"
|
||||
"\n"
|
||||
" Если у вас возникли вопросы по настройке бота, то посмотрите нашу "
|
||||
"справку /help или напишите нам\n"
|
||||
" @civsocit_feedback_bot\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 机器人管理@{0}。\n"
|
||||
"\n"
|
||||
" 如果你有任何关于机器人配置的问题,请参阅我们的帮助/help\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:138
|
||||
msgid "Да, удалить бот"
|
||||
msgstr "是的,删除该机器人"
|
||||
|
||||
#: olgram/commands/menu.py:147
|
||||
msgid ""
|
||||
"\n"
|
||||
" Вы уверены, что хотите удалить бота @{0}?\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 你确定要删除机器人@{0}吗?\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:156
|
||||
msgid "Потоки сообщений"
|
||||
msgstr "信息流"
|
||||
|
||||
#: olgram/commands/menu.py:161
|
||||
msgid "Данные пользователя"
|
||||
msgstr "用户数据"
|
||||
|
||||
#: olgram/commands/menu.py:171 olgram/commands/menu.py:172
|
||||
msgid "включены"
|
||||
msgstr "包括"
|
||||
|
||||
#: olgram/commands/menu.py:171 olgram/commands/menu.py:172
|
||||
msgid "выключены"
|
||||
msgstr "关闭"
|
||||
|
||||
#: olgram/commands/menu.py:173
|
||||
msgid ""
|
||||
"\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#threads"
|
||||
"\">Потоки сообщений</a>: <b>{0}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-info"
|
||||
"\">Данные пользователя</a>: <b>{1}</b>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#threads\">"
|
||||
"信息流</a>: <b>{0}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-info"
|
||||
"\">用户数据</a>: <b>{1}</b>\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:185 olgram/commands/menu.py:247
|
||||
#: olgram/commands/menu.py:289
|
||||
msgid "<< Завершить редактирование"
|
||||
msgstr "<< 完成编辑"
|
||||
|
||||
#: olgram/commands/menu.py:189
|
||||
msgid "Автоответчик"
|
||||
msgstr "自动回复"
|
||||
|
||||
#: olgram/commands/menu.py:194 olgram/commands/menu.py:261
|
||||
msgid "Сбросить текст"
|
||||
msgstr "重置文本"
|
||||
|
||||
#: olgram/commands/menu.py:199
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст, который отправляется после того, как "
|
||||
"пользователь отправит вашему боту @{0}\n"
|
||||
" команду /start\n"
|
||||
"\n"
|
||||
" Текущий текст:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 你现在正在编辑用户向你的机器人发送@{0}之后的文本。\n"
|
||||
" /start\n"
|
||||
"\n"
|
||||
" 目前的文本。\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" 发送消息,改变文本。\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:226
|
||||
msgid ""
|
||||
"\n"
|
||||
" Статистика по боту @{0}\n"
|
||||
"\n"
|
||||
" Входящих сообщений: <b>{1}</b>\n"
|
||||
" Ответных сообщений: <b>{2}</b>\n"
|
||||
" Шаблоны ответов: <b>{3}</b>\n"
|
||||
" Забанено пользователей: <b>{4}</b>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 机器人统计 @{0}\n"
|
||||
"\n"
|
||||
" 收到的信息: <b>{1}</b>\n"
|
||||
" 回复信息: <b>{2}</b>\n"
|
||||
" 回复模板: <b>{3}</b>\n"
|
||||
" 被禁止的用户: <b>{4}</b>\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:251
|
||||
msgid "Предыдущий текст"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:256
|
||||
msgid "Шаблоны ответов..."
|
||||
msgstr "回复模板..."
|
||||
|
||||
#: olgram/commands/menu.py:266
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
|
||||
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
|
||||
"отключено.\n"
|
||||
"\n"
|
||||
" Текущий текст:\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 你现在正在编辑自动回复的文本。该信息会自动响应所有收到的@{0}信息而发送。"
|
||||
"默认情况下,它是禁用的。\n"
|
||||
"\n"
|
||||
" 目前的文本。\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>。\n"
|
||||
" 发送消息,改变文本。\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:276
|
||||
msgid "(отключено)"
|
||||
msgstr "(关闭)"
|
||||
|
||||
#: olgram/commands/menu.py:293
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
|
||||
"\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>\n"
|
||||
" Отправьте какую-нибудь фразу (например: \"Ваш заказ готов, ожидайте!\"), "
|
||||
"чтобы добавить её в шаблон.\n"
|
||||
" Чтобы удалить шаблон из списка, отправьте его номер в списке (например, "
|
||||
"4)\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 你现在正在编辑@{0}的回复模板。目前的模板。\n"
|
||||
"\n"
|
||||
" <pre>\n"
|
||||
" {1}\n"
|
||||
" </pre>。\n"
|
||||
" 发送一个短语(例如:\"您的订单已准备好,请等待!\"),将其添加到模板"
|
||||
"中。\n"
|
||||
" 要从列表中删除一个模板,请发送它在列表中的编号(如4)。\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:312
|
||||
msgid "(нет шаблонов)"
|
||||
msgstr "(没有模板)"
|
||||
|
||||
#: olgram/commands/menu.py:351
|
||||
msgid "У вас нет шаблонов, чтобы их удалять"
|
||||
msgstr "你没有模板来删除它们"
|
||||
|
||||
#: olgram/commands/menu.py:353
|
||||
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
|
||||
msgstr "不正确的数字。要删除一个模式,请在0和{0}之间输入一个数字。"
|
||||
|
||||
#: olgram/commands/menu.py:361
|
||||
msgid "У вашего бота уже слишком много шаблонов"
|
||||
msgstr "你的机器人已经有太多的模式了"
|
||||
|
||||
#: olgram/commands/menu.py:365
|
||||
msgid "Такой текст уже есть в списке шаблонов"
|
||||
msgstr "此文本已在模板列表中"
|
||||
|
||||
#: olgram/commands/menu.py:383
|
||||
msgid "У вас нет прав на этого бота"
|
||||
msgstr "你对这个机器人没有任何权利"
|
||||
|
||||
#: olgram/commands/start.py:23
|
||||
msgid ""
|
||||
"\n"
|
||||
" Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее "
|
||||
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>.\n"
|
||||
"\n"
|
||||
" Используйте эти команды, чтобы управлять этим ботом:\n"
|
||||
"\n"
|
||||
" /addbot - добавить бот\n"
|
||||
" /mybots - управление ботами\n"
|
||||
"\n"
|
||||
" /help - помощь\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Olgram Bot — 是一个Telegram反馈机器人的构建者。阅读更多 <a href="
|
||||
"\"https://olgram.readthedocs.io\">在此阅读</a>.\n"
|
||||
"\n"
|
||||
" 使用这些命令来控制这个机器人:\n"
|
||||
"\n"
|
||||
" /addbot - 绑定机器人\n"
|
||||
" /mybots - 机器人控制\n"
|
||||
"\n"
|
||||
" /help - 帮助\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/start.py:42
|
||||
msgid ""
|
||||
"\n"
|
||||
" Читайте инструкции на нашем сайте https://olgram.readthedocs.io\n"
|
||||
" Техническая поддержка: @civsocit_feedback_bot\n"
|
||||
" Версия {0}\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 请阅读我们网站上的说明 https://olgram.readthedocs.io\n"
|
||||
" 版本{0}\n"
|
||||
" "
|
||||
|
||||
#: olgram/models/models.py:30
|
||||
msgid ""
|
||||
"\n"
|
||||
" Здравствуйте!\n"
|
||||
" Напишите ваш вопрос и мы ответим вам в ближайшее время.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 你好!\n"
|
||||
" 请写下您的问题,我们将很快给您答复。\n"
|
||||
" "
|
||||
|
||||
#: olgram/utils/permissions.py:40
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу 😞"
|
||||
msgstr "机器人所有者已经限制了对该功能的访问 😞"
|
||||
|
||||
#: olgram/utils/permissions.py:52
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу😞"
|
||||
msgstr "机器人主人限制了对该功能的访问😞。"
|
||||
|
||||
#: server/custom.py:40
|
||||
msgid ""
|
||||
"<b>Политика конфиденциальности</b>\n"
|
||||
"\n"
|
||||
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
|
||||
"отправке сообщения (кроме команд /start и /security_policy) ваш "
|
||||
"идентификатор пользователя записывается в кеш на некоторое время и потом "
|
||||
"удаляется из кеша. Этот идентификатор используется только для общения с "
|
||||
"оператором; боты Olgram не делают массовых рассылок.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"<b>隐私政策</b\n"
|
||||
"\n"
|
||||
"这个机器人不存储你的信息、用户名或@用户名。当你发送消息时(除/start和/"
|
||||
"security_policy外),你的用户名会被缓存一段时间,然后从缓存中删除。这个ID只用"
|
||||
"于与运营商沟通;Olgram机器人不做批量信息发送。\n"
|
||||
"\n"
|
||||
|
||||
#: server/custom.py:46
|
||||
msgid ""
|
||||
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
|
||||
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
|
||||
"силу настроек, которые оператор указал при создании бота."
|
||||
msgstr ""
|
||||
"当发送消息时(除了/start和/security_policy),操作者<b>看到</b>你的用户名、@"
|
||||
"用户名和用户ID,凭借的是操作者在创建机器人时指定的设置。"
|
||||
|
||||
#: server/custom.py:50
|
||||
msgid ""
|
||||
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
|
||||
"видеть ваш username, имя пользователя и другую информацию."
|
||||
msgstr ""
|
||||
"根据你的Telegram隐私设置,运营商可能会看到你的用户名,用户名和其他信息。"
|
||||
|
||||
#: server/custom.py:61
|
||||
msgid "Сообщение от пользователя "
|
||||
msgstr "用户的信息 "
|
||||
|
||||
#: server/custom.py:88
|
||||
msgid "Вы заблокированы в этом боте"
|
||||
msgstr "你在这个机器人中被封锁了"
|
||||
|
||||
#: server/custom.py:128
|
||||
msgid ""
|
||||
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
|
||||
"старое?)</i>"
|
||||
msgstr "无法转发信息:找不到作者(信息太旧?)"
|
||||
|
||||
#: server/custom.py:136
|
||||
msgid "Пользователь заблокирован"
|
||||
msgstr "用户被封锁了"
|
||||
|
||||
#: server/custom.py:141
|
||||
msgid "Пользователь не был забанен"
|
||||
msgstr "该用户没有被禁止"
|
||||
|
||||
#: server/custom.py:144
|
||||
msgid "Пользователь разбанен"
|
||||
msgstr "解禁用户"
|
||||
|
||||
#: server/custom.py:149
|
||||
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
|
||||
msgstr "无法转发该信息(作者已经屏蔽了机器人?)"
|
||||
|
||||
#: server/server.py:41
|
||||
msgid "(Пере)запустить бота"
|
||||
msgstr "(重新)启动机器人"
|
||||
|
||||
#: server/server.py:42
|
||||
msgid "Политика конфиденциальности"
|
||||
msgstr "隐私政策"
|
||||
|
||||
|
||||
msgid "\n\nЭтот бот создан с помощью @OlgramBot"
|
||||
msgstr "\n\n "
|
39
main.py
@ -1,34 +1,38 @@
|
||||
import asyncio
|
||||
import argparse
|
||||
from tortoise import Tortoise
|
||||
|
||||
from olgram.router import dp
|
||||
from olgram.settings import TORTOISE_ORM
|
||||
from olgram.settings import TORTOISE_ORM, OlgramSettings
|
||||
from olgram.utils.permissions import AccessMiddleware
|
||||
from server.custom import init_redis
|
||||
|
||||
import olgram.commands.bots # noqa: F401
|
||||
import olgram.commands.start # noqa: F401
|
||||
import olgram.commands.menu # noqa: F401
|
||||
import olgram.commands.bot_actions # noqa: F401
|
||||
import olgram.commands.info # noqa: F401
|
||||
import olgram.commands.promo # noqa: F401
|
||||
import olgram.commands.admin # noqa: F401
|
||||
from locales.locale import _
|
||||
|
||||
from server.server import main as server_main
|
||||
|
||||
import logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
async def init_database():
|
||||
await Tortoise.init(config=TORTOISE_ORM)
|
||||
|
||||
|
||||
async def init_olgram():
|
||||
from olgram.router import bot
|
||||
from olgram.router import bot, dp
|
||||
dp.setup_middleware(AccessMiddleware(OlgramSettings.admin_ids()))
|
||||
from aiogram.types import BotCommand
|
||||
await bot.set_my_commands(
|
||||
[
|
||||
BotCommand("start", "Запустить бота"),
|
||||
BotCommand("addbot", "Добавить бот"),
|
||||
BotCommand("mybots", "Управление ботами"),
|
||||
BotCommand("help", "Справка")
|
||||
BotCommand("start", _("Запустить бота")),
|
||||
BotCommand("addbot", _("Добавить бот")),
|
||||
BotCommand("mybots", _("Управление ботами")),
|
||||
BotCommand("help", _("Справка"))
|
||||
]
|
||||
)
|
||||
|
||||
@ -40,14 +44,21 @@ async def initialization():
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Classic polling
|
||||
"""
|
||||
parser = argparse.ArgumentParser("Olgram bot and feedback server")
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--noserver", help="Не запускать сервер обратной связи, только сам Olgram", action="store_true")
|
||||
group.add_argument("--onlyserver", help="Запустить только сервер обратной связи, без Olgram", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(initialization())
|
||||
|
||||
loop.create_task(dp.start_polling())
|
||||
loop.create_task(server_main().start())
|
||||
if not args.onlyserver:
|
||||
print("Run olgram polling")
|
||||
loop.create_task(dp.start_polling())
|
||||
if not args.noserver:
|
||||
print("Run olgram server")
|
||||
loop.create_task(server_main().start())
|
||||
|
||||
loop.run_forever()
|
||||
|
||||
|
Before Width: | Height: | Size: 202 KiB |
6
migrate.py
Normal file
@ -0,0 +1,6 @@
|
||||
import asyncio
|
||||
from olgram.migrations.custom import migrate
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.get_event_loop().run_until_complete(migrate())
|
81
olgram/commands/admin.py
Normal file
@ -0,0 +1,81 @@
|
||||
"""
|
||||
Здесь некоторые команды администратора
|
||||
"""
|
||||
|
||||
from aiogram import types
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from olgram.models import models
|
||||
|
||||
from olgram.router import dp
|
||||
from olgram.settings import OlgramSettings
|
||||
from locales.locale import _
|
||||
|
||||
|
||||
@dp.message_handler(commands=["notifyowner"], state="*")
|
||||
async def notify(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Команда /notify-owner
|
||||
"""
|
||||
|
||||
if message.chat.id != OlgramSettings.supervisor_id():
|
||||
await message.answer(_("Недостаточно прав"))
|
||||
return
|
||||
|
||||
bot_name = message.get_args()
|
||||
|
||||
if not bot_name:
|
||||
await message.answer(_("Нужно указать имя бота"))
|
||||
return
|
||||
|
||||
bot = await models.Bot.filter(name=bot_name.replace("@", "")).first()
|
||||
|
||||
if not bot:
|
||||
await message.answer(_("Такого бота нет в системе"))
|
||||
return
|
||||
|
||||
await state.set_state("wait_owner_notify_message")
|
||||
await state.update_data({"notify_to_bot": bot.id})
|
||||
|
||||
markup = types.ReplyKeyboardMarkup([[types.KeyboardButton(text=_("Пропустить"))]],
|
||||
resize_keyboard=True)
|
||||
|
||||
await message.answer(_("Введите текст, который будет отправлен владельцу бота {0}. "
|
||||
"Напишите 'Пропустить' чтобы отменить").format(bot_name), reply_markup=markup)
|
||||
|
||||
|
||||
@dp.message_handler(state="wait_owner_notify_message")
|
||||
async def on_notify_text(message: types.Message, state: FSMContext):
|
||||
if not message.text:
|
||||
await state.reset_state(with_data=True)
|
||||
await message.answer(_("Поддерживается только текст"), reply_markup=types.ReplyKeyboardRemove())
|
||||
return
|
||||
|
||||
if message.text == _("Пропустить"):
|
||||
await state.reset_state(with_data=True)
|
||||
await message.answer(_("Отменено"), reply_markup=types.ReplyKeyboardRemove())
|
||||
return
|
||||
|
||||
await state.update_data({"notify_text": message.text})
|
||||
await state.set_state("wait_owner_notify_message_confirm")
|
||||
|
||||
markup = types.ReplyKeyboardMarkup([[types.KeyboardButton(text=_("Отправить")),
|
||||
types.KeyboardButton(text=_("Отменить"))]], resize_keyboard=True)
|
||||
|
||||
await message.answer("Точно отправить?", reply_markup=markup)
|
||||
|
||||
|
||||
@dp.message_handler(state="wait_owner_notify_message_confirm")
|
||||
async def on_notify_message_confirm(message: types.Message, state: FSMContext):
|
||||
if not message.text or (message.text != _("Отправить")):
|
||||
await state.reset_state(with_data=True)
|
||||
await message.answer(_("Отменено"), reply_markup=types.ReplyKeyboardRemove())
|
||||
return
|
||||
|
||||
data = await state.get_data()
|
||||
bot = await models.Bot.get(pk=data["notify_to_bot"])
|
||||
text = data["notify_text"]
|
||||
chat_id = (await bot.owner).telegram_id
|
||||
|
||||
await state.reset_state(with_data=True)
|
||||
await message.bot.send_message(chat_id, text=text)
|
||||
await message.answer(_("Отправлено"), reply_markup=types.ReplyKeyboardRemove())
|
@ -2,18 +2,24 @@
|
||||
Здесь работа с конкретным ботом
|
||||
"""
|
||||
from aiogram import types
|
||||
from aiogram.utils.exceptions import TelegramAPIError
|
||||
from aiogram.utils.exceptions import TelegramAPIError, Unauthorized
|
||||
from aiogram import Bot as AioBot
|
||||
from olgram.models.models import Bot
|
||||
from server.server import unregister_token
|
||||
from locales.locale import _
|
||||
|
||||
|
||||
async def delete_bot(bot: Bot, call: types.CallbackQuery):
|
||||
"""
|
||||
Пользователь решил удалить бота
|
||||
"""
|
||||
await unregister_token(bot.token)
|
||||
try:
|
||||
await unregister_token(bot.decrypted_token())
|
||||
except Unauthorized:
|
||||
# Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы
|
||||
pass
|
||||
await bot.delete()
|
||||
await call.answer("Бот удалён")
|
||||
await call.answer(_("Бот удалён"))
|
||||
try:
|
||||
await call.message.delete()
|
||||
except TelegramAPIError:
|
||||
@ -29,7 +35,19 @@ async def reset_bot_text(bot: Bot, call: types.CallbackQuery):
|
||||
"""
|
||||
bot.start_text = bot._meta.fields_map['start_text'].default
|
||||
await bot.save()
|
||||
await call.answer("Текст сброшен")
|
||||
await call.answer(_("Текст сброшен"))
|
||||
|
||||
|
||||
async def reset_bot_second_text(bot: Bot, call: types.CallbackQuery):
|
||||
"""
|
||||
Пользователь решил сбросить second text бота
|
||||
:param bot:
|
||||
:param call:
|
||||
:return:
|
||||
"""
|
||||
bot.second_text = bot._meta.fields_map['second_text'].default
|
||||
await bot.save()
|
||||
await call.answer(_("Текст сброшен"))
|
||||
|
||||
|
||||
async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str):
|
||||
@ -43,13 +61,48 @@ async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str):
|
||||
if chat == "personal":
|
||||
bot.group_chat = None
|
||||
await bot.save()
|
||||
await call.answer("Выбран личный чат")
|
||||
await call.answer(_("Выбран личный чат"))
|
||||
return
|
||||
if chat == "leave":
|
||||
bot.group_chat = None
|
||||
await bot.save()
|
||||
chats = await bot.group_chats.all()
|
||||
a_bot = AioBot(bot.decrypted_token())
|
||||
for chat in chats:
|
||||
try:
|
||||
await chat.delete()
|
||||
await a_bot.leave_chat(chat.chat_id)
|
||||
except TelegramAPIError:
|
||||
pass
|
||||
await call.answer(_("Бот вышел из чатов"))
|
||||
await a_bot.session.close()
|
||||
return
|
||||
|
||||
chat_obj = await bot.group_chats.filter(id=chat).first()
|
||||
if not chat_obj:
|
||||
await call.answer("Нельзя привязать бота к этому чату")
|
||||
await call.answer(_("Нельзя привязать бота к этому чату"))
|
||||
return
|
||||
bot.group_chat = chat_obj
|
||||
await bot.save()
|
||||
await call.answer(f"Выбран чат {chat_obj.name}")
|
||||
await call.answer(_("Выбран чат {0}").format(chat_obj.name))
|
||||
|
||||
|
||||
async def threads(bot: Bot, call: types.CallbackQuery):
|
||||
bot.enable_threads = not bot.enable_threads
|
||||
await bot.save(update_fields=["enable_threads"])
|
||||
|
||||
|
||||
async def additional_info(bot: Bot, call: types.CallbackQuery):
|
||||
bot.enable_additional_info = not bot.enable_additional_info
|
||||
await bot.save(update_fields=["enable_additional_info"])
|
||||
|
||||
|
||||
async def olgram_text(bot: Bot, call: types.CallbackQuery):
|
||||
if await bot.is_promo():
|
||||
bot.enable_olgram_text = not bot.enable_olgram_text
|
||||
await bot.save(update_fields=["enable_olgram_text"])
|
||||
|
||||
|
||||
async def antiflood(bot: Bot, call: types.CallbackQuery):
|
||||
bot.enable_antiflood = not bot.enable_antiflood
|
||||
await bot.save(update_fields=["enable_antiflood"])
|
||||
|
@ -9,9 +9,10 @@ import re
|
||||
from textwrap import dedent
|
||||
|
||||
from olgram.models.models import Bot, User
|
||||
from olgram.settings import OlgramSettings
|
||||
from olgram.settings import OlgramSettings, BotSettings
|
||||
from olgram.commands.menu import send_bots_menu
|
||||
from server.server import register_token
|
||||
from locales.locale import _
|
||||
|
||||
from olgram.router import dp
|
||||
|
||||
@ -36,12 +37,17 @@ async def add_bot(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Команда /addbot (добавить бота)
|
||||
"""
|
||||
user = await User.get_or_none(telegram_id=message.from_user.id)
|
||||
max_bot_count = OlgramSettings.max_bots_per_user()
|
||||
if user and await user.is_promo():
|
||||
max_bot_count = OlgramSettings.max_bots_per_user_promo()
|
||||
bot_count = await Bot.filter(owner__telegram_id=message.from_user.id).count()
|
||||
if bot_count >= OlgramSettings.max_bots_per_user():
|
||||
await message.answer("У вас уже слишком много ботов.")
|
||||
if bot_count >= max_bot_count:
|
||||
await message.answer(_("У вас уже слишком много ботов. Удалите какой-нибудь свой бот из Olgram"
|
||||
"(/mybots -> (Выбрать бота) -> Удалить бот)"))
|
||||
return
|
||||
|
||||
await message.answer(dedent("""
|
||||
await message.answer(dedent(_("""
|
||||
Чтобы подключить бот, вам нужно выполнить три действия:
|
||||
|
||||
1. Перейдите в бот @BotFather, нажмите START и отправьте команду /newbot
|
||||
@ -49,7 +55,7 @@ async def add_bot(message: types.Message, state: FSMContext):
|
||||
3. После создания бота перешлите ответное сообщение в этот бот или скопируйте и пришлите token бота.
|
||||
|
||||
Важно: не подключайте боты, которые используются в других сервисах (Manybot, Chatfuel, Livegram и других).
|
||||
"""))
|
||||
""")))
|
||||
await state.set_state("add_bot")
|
||||
|
||||
|
||||
@ -61,26 +67,26 @@ async def bot_added(message: types.Message, state: FSMContext):
|
||||
token = re.findall(token_pattern, message.text)
|
||||
|
||||
async def on_invalid_token():
|
||||
await message.answer(dedent("""
|
||||
await message.answer(dedent(_("""
|
||||
Это не токен бота.
|
||||
|
||||
Токен выглядит вот так: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12
|
||||
"""))
|
||||
""")))
|
||||
|
||||
async def on_dummy_token():
|
||||
await message.answer(dedent("""
|
||||
await message.answer(dedent(_("""
|
||||
Не удалось запустить этого бота: неверный токен
|
||||
"""))
|
||||
""")))
|
||||
|
||||
async def on_unknown_error():
|
||||
await message.answer(dedent("""
|
||||
await message.answer(dedent(_("""
|
||||
Не удалось запустить этого бота: непредвиденная ошибка
|
||||
"""))
|
||||
""")))
|
||||
|
||||
async def on_duplication_bot():
|
||||
await message.answer(dedent("""
|
||||
await message.answer(dedent(_("""
|
||||
Такой бот уже есть в базе данных
|
||||
"""))
|
||||
""")))
|
||||
|
||||
if not token:
|
||||
return await on_invalid_token()
|
||||
@ -98,8 +104,12 @@ async def bot_added(message: types.Message, state: FSMContext):
|
||||
except TelegramAPIError:
|
||||
return await on_unknown_error()
|
||||
|
||||
user, _ = await User.get_or_create(telegram_id=message.from_user.id)
|
||||
bot = Bot(token=token, owner=user, name=test_bot_info.username, super_chat_id=message.from_user.id)
|
||||
if token == BotSettings.token():
|
||||
return await on_duplication_bot()
|
||||
|
||||
user, created = await User.get_or_create(telegram_id=message.from_user.id)
|
||||
bot = Bot(token=Bot.encrypted_token(token), owner=user, name=test_bot_info.username,
|
||||
super_chat_id=message.from_user.id)
|
||||
try:
|
||||
await bot.save()
|
||||
except IntegrityError:
|
||||
@ -109,5 +119,5 @@ async def bot_added(message: types.Message, state: FSMContext):
|
||||
await bot.delete()
|
||||
return await on_unknown_error()
|
||||
|
||||
await message.answer("Бот добавлен! Список ваших ботов: /mybots")
|
||||
await message.answer(_("Бот добавлен! Список ваших ботов: /mybots"))
|
||||
await state.reset_state()
|
||||
|
40
olgram/commands/info.py
Normal file
@ -0,0 +1,40 @@
|
||||
"""
|
||||
Здесь метрики
|
||||
"""
|
||||
|
||||
from aiogram import types
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from olgram.models import models
|
||||
|
||||
from olgram.router import dp
|
||||
from olgram.settings import OlgramSettings
|
||||
from locales.locale import _
|
||||
|
||||
|
||||
@dp.message_handler(commands=["info"], state="*")
|
||||
async def info(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Команда /info
|
||||
"""
|
||||
|
||||
if message.chat.id != OlgramSettings.supervisor_id():
|
||||
await message.answer(_("Недостаточно прав"))
|
||||
return
|
||||
|
||||
bots = await models.Bot.all()
|
||||
bots_count = len(bots)
|
||||
user_count = len(await models.User.all())
|
||||
templates_count = len(await models.DefaultAnswer.all())
|
||||
promo_count = len(await models.Promo.all())
|
||||
olgram_text_disabled = len(await models.Bot.filter(enable_olgram_text=False))
|
||||
|
||||
income_messages = sum([bot.incoming_messages_count for bot in bots])
|
||||
outgoing_messages = sum([bot.outgoing_messages_count for bot in bots])
|
||||
|
||||
await message.answer(_("Количество ботов: {0}\n").format(bots_count) +
|
||||
_("Количество пользователей (у конструктора): {0}\n").format(user_count) +
|
||||
_("Шаблонов ответов: {0}\n").format(templates_count) +
|
||||
_("Входящих сообщений у всех ботов: {0}\n").format(income_messages) +
|
||||
_("Исходящих сообщений у всех ботов: {0}\n").format(outgoing_messages) +
|
||||
_("Промо-кодов выдано: {0}\n").format(promo_count) +
|
||||
_("Рекламную плашку выключили: {0}\n".format(olgram_text_disabled)))
|
@ -1,12 +1,13 @@
|
||||
from olgram.router import dp
|
||||
|
||||
from aiogram import types, Bot as AioBot
|
||||
from olgram.models.models import Bot, User
|
||||
from olgram.models.models import Bot, User, DefaultAnswer
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
from textwrap import dedent
|
||||
from olgram.utils.mix import edit_or_create, button_text_limit
|
||||
from olgram.utils.mix import edit_or_create, button_text_limit, wrap
|
||||
from olgram.commands import bot_actions
|
||||
from locales.locale import _
|
||||
|
||||
import typing as ty
|
||||
|
||||
@ -27,11 +28,11 @@ async def send_bots_menu(chat_id: int, user_id: int, call=None):
|
||||
user = await User.get_or_none(telegram_id=user_id)
|
||||
bots = await Bot.filter(owner=user)
|
||||
if not bots:
|
||||
await AioBot.get_current().send_message(chat_id, dedent("""
|
||||
await AioBot.get_current().send_message(chat_id, dedent(_("""
|
||||
У вас нет добавленных ботов.
|
||||
|
||||
Отправьте команду /addbot, чтобы добавить бот.
|
||||
"""))
|
||||
""")))
|
||||
return
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||
@ -42,7 +43,7 @@ async def send_bots_menu(chat_id: int, user_id: int, call=None):
|
||||
chat=empty))
|
||||
)
|
||||
|
||||
text = "Ваши боты"
|
||||
text = _("Ваши боты")
|
||||
if call:
|
||||
await edit_or_create(call, text, keyboard)
|
||||
else:
|
||||
@ -63,26 +64,33 @@ async def send_chats_menu(bot: Bot, call: types.CallbackQuery):
|
||||
)
|
||||
if chats:
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="Личные сообщения",
|
||||
types.InlineKeyboardButton(text=_("Личные сообщения"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
|
||||
chat="personal"))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("❗️ Выйти из всех чатов"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
|
||||
chat="leave"))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="<< Назад",
|
||||
types.InlineKeyboardButton(text=_("<< Назад"),
|
||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty,
|
||||
chat=empty))
|
||||
)
|
||||
|
||||
if not chats:
|
||||
text = dedent(f"""
|
||||
text = dedent(_("""
|
||||
Этот бот не добавлен в чаты, поэтому все сообщения будут приходить вам в бот.
|
||||
Чтобы подключить чат — просто добавьте бот @{bot.name} в чат.
|
||||
""")
|
||||
Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню ещё раз и выберите добавленный чат.
|
||||
Если ваш бот состоял в групповом чате до того, как его добавили в Olgram - удалите бота из чата и добавьте
|
||||
снова.
|
||||
""")).format(bot.name)
|
||||
else:
|
||||
text = dedent(f"""
|
||||
В этом разделе вы можете привязать бота @{bot.name} к чату.
|
||||
text = dedent(_("""
|
||||
В этом разделе вы можете привязать бота @{0} к чату.
|
||||
Выберите чат, куда бот будет пересылать сообщения.
|
||||
""")
|
||||
""")).format(bot.name)
|
||||
|
||||
await edit_or_create(call, text, keyboard)
|
||||
|
||||
@ -91,49 +99,107 @@ async def send_bot_menu(bot: Bot, call: types.CallbackQuery):
|
||||
await call.answer()
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="Текст",
|
||||
types.InlineKeyboardButton(text=_("Текст"),
|
||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="Чат",
|
||||
types.InlineKeyboardButton(text=_("Чат"),
|
||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="chat",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="Удалить бот",
|
||||
types.InlineKeyboardButton(text=_("Удалить бот"),
|
||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="delete",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="<< Назад",
|
||||
types.InlineKeyboardButton(text=_("Статистика"),
|
||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="stat",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("<< Назад"),
|
||||
callback_data=menu_callback.new(level=0, bot_id=empty, operation=empty, chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Опции"),
|
||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="settings",
|
||||
chat=empty))
|
||||
)
|
||||
|
||||
await edit_or_create(call, dedent(f"""
|
||||
Управление ботом @{bot.name}.
|
||||
await edit_or_create(call, dedent(_("""
|
||||
Управление ботом @{0}.
|
||||
|
||||
Если у вас возникли вопросы по настройке бота, то посмотрите нашу справку /help или напишите нам
|
||||
@civsocit_feedback_bot
|
||||
"""), reply_markup=keyboard)
|
||||
""")).format(bot.name), reply_markup=keyboard)
|
||||
|
||||
|
||||
async def send_bot_delete_menu(bot: Bot, call: types.CallbackQuery):
|
||||
await call.answer()
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="Да, удалить бот",
|
||||
types.InlineKeyboardButton(text=_("Да, удалить бот"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="delete_yes",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="<< Назад",
|
||||
types.InlineKeyboardButton(text=_("<< Назад"),
|
||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
||||
)
|
||||
|
||||
await edit_or_create(call, dedent(f"""
|
||||
Вы уверены, что хотите удалить бота @{bot.name}?
|
||||
"""), reply_markup=keyboard)
|
||||
await edit_or_create(call, dedent(_("""
|
||||
Вы уверены, что хотите удалить бота @{0}?
|
||||
""")).format(bot.name), reply_markup=keyboard)
|
||||
|
||||
|
||||
async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
|
||||
await call.answer()
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Потоки сообщений"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="threads",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Данные пользователя"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="additional_info",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Антифлуд"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="antiflood",
|
||||
chat=empty))
|
||||
)
|
||||
is_promo = await bot.is_promo()
|
||||
if is_promo:
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Olgram подпись"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="olgram_text",
|
||||
chat=empty))
|
||||
)
|
||||
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("<< Назад"),
|
||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty,
|
||||
chat=empty))
|
||||
)
|
||||
|
||||
thread_turn = _("включены") if bot.enable_threads else _("выключены")
|
||||
info_turn = _("включены") if bot.enable_additional_info else _("выключены")
|
||||
antiflood_turn = _("включен") if bot.enable_antiflood else _("выключен")
|
||||
text = dedent(_("""
|
||||
<a href="https://olgram.readthedocs.io/ru/latest/options.html#threads">Потоки сообщений</a>: <b>{0}</b>
|
||||
<a href="https://olgram.readthedocs.io/ru/latest/options.html#user-info">Данные пользователя</a>: <b>{1}</b>
|
||||
<a href="https://olgram.readthedocs.io/ru/latest/options.html#antiflood">Антифлуд</a>: <b>{2}</b>
|
||||
""")).format(thread_turn, info_turn, antiflood_turn)
|
||||
|
||||
if is_promo:
|
||||
olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена")
|
||||
text += _("Olgram подпись: <b>{0}</b>").format(olgram_turn)
|
||||
|
||||
await edit_or_create(call, text, reply_markup=keyboard, parse_mode="HTML")
|
||||
|
||||
|
||||
async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, chat_id: ty.Optional[int] = None):
|
||||
@ -141,17 +207,22 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] =
|
||||
await call.answer()
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="Сбросить текст",
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="reset_text",
|
||||
types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
|
||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Автоответчик"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="next_text",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text="<< Завершить редактирование",
|
||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
||||
types.InlineKeyboardButton(text=_("Сбросить текст"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="reset_text",
|
||||
chat=empty))
|
||||
)
|
||||
|
||||
text = dedent("""
|
||||
Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту {0}
|
||||
text = dedent(_("""
|
||||
Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту @{0}
|
||||
команду /start
|
||||
|
||||
Текущий текст:
|
||||
@ -159,7 +230,7 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] =
|
||||
{1}
|
||||
</pre>
|
||||
Отправьте сообщение, чтобы изменить текст.
|
||||
""")
|
||||
"""))
|
||||
text = text.format(bot.name, bot.start_text)
|
||||
if call:
|
||||
await edit_or_create(call, text, keyboard, parse_mode="HTML")
|
||||
@ -167,16 +238,163 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] =
|
||||
await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML")
|
||||
|
||||
|
||||
async def send_bot_statistic_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
|
||||
chat_id: ty.Optional[int] = None):
|
||||
if call:
|
||||
await call.answer()
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("<< Назад"),
|
||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
||||
)
|
||||
|
||||
text = dedent(_("""
|
||||
Статистика по боту @{0}
|
||||
|
||||
Входящих сообщений: <b>{1}</b>
|
||||
Ответных сообщений: <b>{2}</b>
|
||||
Шаблоны ответов: <b>{3}</b>
|
||||
Забанено пользователей: <b>{4}</b>
|
||||
""")).format(bot.name, bot.incoming_messages_count, bot.outgoing_messages_count, len(await bot.answers),
|
||||
len(await bot.banned_users))
|
||||
if call:
|
||||
await edit_or_create(call, text, keyboard, parse_mode="HTML")
|
||||
else:
|
||||
await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML")
|
||||
|
||||
|
||||
async def send_bot_second_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
|
||||
chat_id: ty.Optional[int] = None):
|
||||
if call:
|
||||
await call.answer()
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
|
||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Предыдущий текст"),
|
||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Шаблоны ответов..."),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="templates",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Сбросить текст"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
||||
operation="reset_second_text", chat=empty))
|
||||
)
|
||||
|
||||
text = dedent(_("""
|
||||
Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в ответ на все входящие сообщения @{0} \
|
||||
автоматически. По умолчанию оно отключено.
|
||||
|
||||
Текущий текст:
|
||||
<pre>
|
||||
{1}
|
||||
</pre>
|
||||
Отправьте сообщение, чтобы изменить текст.
|
||||
"""))
|
||||
text = text.format(bot.name, bot.second_text if bot.second_text else _("(отключено)"))
|
||||
if call:
|
||||
await edit_or_create(call, text, keyboard, parse_mode="HTML")
|
||||
else:
|
||||
await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML")
|
||||
|
||||
|
||||
async def send_bot_templates_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
|
||||
chat_id: ty.Optional[int] = None):
|
||||
if call:
|
||||
await call.answer()
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
|
||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
||||
)
|
||||
|
||||
text = dedent(_("""
|
||||
Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:
|
||||
|
||||
<pre>
|
||||
{1}
|
||||
</pre>
|
||||
Отправьте какую-нибудь фразу (например: "Ваш заказ готов, ожидайте!"), чтобы добавить её в шаблон.
|
||||
Чтобы удалить шаблон из списка, отправьте его номер в списке (например, 4)
|
||||
"""))
|
||||
|
||||
templates = await bot.answers
|
||||
|
||||
total_text_len = sum(len(t.text) for t in templates) + len(text) # примерная длина текста
|
||||
max_len = 1000
|
||||
if total_text_len > 4000:
|
||||
max_len = 100
|
||||
|
||||
templates_text = "\n".join(f"{n}. {wrap(template.text, max_len)}" for n, template in enumerate(templates))
|
||||
if not templates_text:
|
||||
templates_text = _("(нет шаблонов)")
|
||||
text = text.format(bot.name, templates_text)
|
||||
if call:
|
||||
await edit_or_create(call, text, keyboard, parse_mode="HTML")
|
||||
else:
|
||||
await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML")
|
||||
|
||||
|
||||
@dp.message_handler(state="wait_start_text", content_types="text", regexp="^[^/].+") # Not command
|
||||
async def start_text_received(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as proxy:
|
||||
bot_id = proxy.get("bot_id")
|
||||
bot = await Bot.get_or_none(pk=bot_id)
|
||||
bot.start_text = message.text
|
||||
bot.start_text = message.html_text
|
||||
await bot.save()
|
||||
await send_bot_text_menu(bot, chat_id=message.chat.id)
|
||||
|
||||
|
||||
@dp.message_handler(state="wait_second_text", content_types="text", regexp="^[^/].+") # Not command
|
||||
async def second_text_received(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as proxy:
|
||||
bot_id = proxy.get("bot_id")
|
||||
bot = await Bot.get_or_none(pk=bot_id)
|
||||
bot.second_text = message.html_text
|
||||
await bot.save()
|
||||
await send_bot_second_text_menu(bot, chat_id=message.chat.id)
|
||||
|
||||
|
||||
@dp.message_handler(state="wait_template", content_types="text", regexp="^[^/](.+)?") # Not command
|
||||
async def template_received(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as proxy:
|
||||
bot_id = proxy.get("bot_id")
|
||||
bot = await Bot.get_or_none(pk=bot_id)
|
||||
|
||||
if message.text.isdigit():
|
||||
# Delete template
|
||||
number = int(message.text)
|
||||
templates = await bot.answers
|
||||
if not templates:
|
||||
await message.answer(_("У вас нет шаблонов, чтобы их удалять"))
|
||||
if number < 0 or number >= len(templates):
|
||||
await message.answer(_("Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}").format(
|
||||
len(templates)))
|
||||
return
|
||||
await templates[number].delete()
|
||||
else:
|
||||
# Add template
|
||||
total_templates = len(await bot.answers)
|
||||
if total_templates > 30:
|
||||
await message.answer(_("У вашего бота уже слишком много шаблонов"))
|
||||
else:
|
||||
answers = await bot.answers.filter(text=message.text)
|
||||
if answers:
|
||||
await message.answer(_("Такой текст уже есть в списке шаблонов"))
|
||||
else:
|
||||
template = DefaultAnswer(text=message.text, bot=bot)
|
||||
await template.save()
|
||||
|
||||
await send_bot_templates_menu(bot, chat_id=message.chat.id)
|
||||
|
||||
|
||||
@dp.callback_query_handler(menu_callback.filter(), state="*")
|
||||
async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMContext):
|
||||
level = callback_data.get("level")
|
||||
@ -187,10 +405,11 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
|
||||
bot_id = callback_data.get("bot_id")
|
||||
bot = await Bot.get_or_none(id=bot_id)
|
||||
if not bot or (await bot.owner).telegram_id != call.from_user.id:
|
||||
await call.answer("У вас нет прав на этого бота", show_alert=True)
|
||||
await call.answer(_("У вас нет прав на этого бота"), show_alert=True)
|
||||
return
|
||||
|
||||
if level == "1":
|
||||
await state.reset_state()
|
||||
return await send_bot_menu(bot, call)
|
||||
|
||||
operation = callback_data.get("operation")
|
||||
@ -200,6 +419,10 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
|
||||
return await send_chats_menu(bot, call)
|
||||
if operation == "delete":
|
||||
return await send_bot_delete_menu(bot, call)
|
||||
if operation == "stat":
|
||||
return await send_bot_statistic_menu(bot, call)
|
||||
if operation == "settings":
|
||||
return await send_bot_settings_menu(bot, call)
|
||||
if operation == "text":
|
||||
await state.set_state("wait_start_text")
|
||||
async with state.proxy() as proxy:
|
||||
@ -211,6 +434,31 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
|
||||
return await bot_actions.delete_bot(bot, call)
|
||||
if operation == "chat":
|
||||
return await bot_actions.select_chat(bot, call, callback_data.get("chat"))
|
||||
if operation == "threads":
|
||||
await bot_actions.threads(bot, call)
|
||||
return await send_bot_settings_menu(bot, call)
|
||||
if operation == "antiflood":
|
||||
await bot_actions.antiflood(bot, call)
|
||||
return await send_bot_settings_menu(bot, call)
|
||||
if operation == "additional_info":
|
||||
await bot_actions.additional_info(bot, call)
|
||||
return await send_bot_settings_menu(bot, call)
|
||||
if operation == "olgram_text":
|
||||
await bot_actions.olgram_text(bot, call)
|
||||
return await send_bot_settings_menu(bot, call)
|
||||
if operation == "reset_text":
|
||||
await bot_actions.reset_bot_text(bot, call)
|
||||
return await send_bot_text_menu(bot, call)
|
||||
if operation == "next_text":
|
||||
await state.set_state("wait_second_text")
|
||||
async with state.proxy() as proxy:
|
||||
proxy["bot_id"] = bot.id
|
||||
return await send_bot_second_text_menu(bot, call)
|
||||
if operation == "reset_second_text":
|
||||
await bot_actions.reset_bot_second_text(bot, call)
|
||||
return await send_bot_second_text_menu(bot, call)
|
||||
if operation == "templates":
|
||||
await state.set_state("wait_template")
|
||||
async with state.proxy() as proxy:
|
||||
proxy["bot_id"] = bot.id
|
||||
return await send_bot_templates_menu(bot, call)
|
||||
|
91
olgram/commands/promo.py
Normal file
@ -0,0 +1,91 @@
|
||||
"""
|
||||
Здесь промокоды
|
||||
"""
|
||||
|
||||
|
||||
from aiogram import types
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from olgram.models import models
|
||||
from uuid import UUID
|
||||
|
||||
from olgram.router import dp
|
||||
from olgram.settings import OlgramSettings
|
||||
from locales.locale import _
|
||||
|
||||
|
||||
@dp.message_handler(commands=["newpromo"], state="*")
|
||||
async def new_promo(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Команда /newpromo
|
||||
"""
|
||||
|
||||
if message.chat.id != OlgramSettings.supervisor_id():
|
||||
await message.answer(_("Недостаточно прав"))
|
||||
return
|
||||
|
||||
promo = await models.Promo()
|
||||
await message.answer(_("Новый промокод\n```{0}```").format(promo.code), parse_mode="Markdown")
|
||||
|
||||
await promo.save()
|
||||
|
||||
|
||||
@dp.message_handler(commands=["delpromo"], state="*")
|
||||
async def del_promo(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Команда /delpromo
|
||||
"""
|
||||
|
||||
if message.chat.id != OlgramSettings.supervisor_id():
|
||||
await message.answer(_("Недостаточно прав"))
|
||||
return
|
||||
|
||||
try:
|
||||
uuid = UUID(message.get_args().strip())
|
||||
promo = await models.Promo.get_or_none(code=uuid)
|
||||
except ValueError:
|
||||
return await message.answer(_("Неправильный токен"))
|
||||
|
||||
if not promo:
|
||||
return await message.answer(_("Такого кода не существует"))
|
||||
|
||||
user = await models.User.filter(promo=promo)
|
||||
bots = await user.bots()
|
||||
for bot in bots:
|
||||
bot.enable_olgram_text = True
|
||||
await bot.save(update_fields=["enable_olgram_text"])
|
||||
|
||||
await promo.delete()
|
||||
|
||||
await message.answer(_("Промокод отозван"))
|
||||
|
||||
|
||||
@dp.message_handler(commands=["setpromo"], state="*")
|
||||
async def setpromo(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Команда /setpromo
|
||||
"""
|
||||
|
||||
arg = message.get_args()
|
||||
if not arg:
|
||||
return await message.answer(_("Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>"),
|
||||
parse_mode="HTML")
|
||||
|
||||
arg = arg.strip()
|
||||
|
||||
try:
|
||||
UUID(arg)
|
||||
except ValueError:
|
||||
return await message.answer(_("Промокод не найден"))
|
||||
|
||||
promo = await models.Promo.get_or_none(code=arg)
|
||||
if not promo:
|
||||
return await message.answer(_("Промокод не найден"))
|
||||
|
||||
if promo.owner:
|
||||
return await message.answer(_("Промокод уже использован"))
|
||||
|
||||
user, created = await models.User.get_or_create(telegram_id=message.from_user.id)
|
||||
promo.owner = user
|
||||
await promo.save(update_fields=["owner_id"])
|
||||
|
||||
await message.answer(_("Промокод активирован! Спасибо 🙌"))
|
@ -6,21 +6,24 @@ from aiogram import types
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from textwrap import dedent
|
||||
from olgram.settings import OlgramSettings
|
||||
from olgram.utils.permissions import public
|
||||
from locales.locale import _
|
||||
|
||||
from olgram.router import dp
|
||||
|
||||
|
||||
@dp.message_handler(commands=["start"], state="*")
|
||||
@public()
|
||||
async def start(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Команда /start
|
||||
"""
|
||||
await state.reset_state()
|
||||
|
||||
# TODO: locale
|
||||
|
||||
await message.answer(dedent("""
|
||||
Olgram Bot — это конструктор ботов обратной связи в Telegram.
|
||||
await message.answer(dedent(_("""
|
||||
Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее \
|
||||
<a href="https://olgram.readthedocs.io">читайте здесь</a>. Следите за обновлениями \
|
||||
<a href="https://t.me/civsoc_it">здесь</a>.
|
||||
|
||||
Используйте эти команды, чтобы управлять этим ботом:
|
||||
|
||||
@ -28,20 +31,26 @@ async def start(message: types.Message, state: FSMContext):
|
||||
/mybots - управление ботами
|
||||
|
||||
/help - помощь
|
||||
"""))
|
||||
""")), parse_mode="html", disable_web_page_preview=True)
|
||||
|
||||
|
||||
@dp.message_handler(commands=["help"], state="*")
|
||||
@public()
|
||||
async def help(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Команда /help
|
||||
"""
|
||||
await message.answer(dedent(f"""
|
||||
О проекте https://telegra.ph/Olgram-09-15
|
||||
await message.answer(dedent(_("""
|
||||
Читайте инструкции на нашем сайте https://olgram.readthedocs.io
|
||||
Техническая поддержка: @civsocit_feedback_bot
|
||||
Версия {0}
|
||||
""")).format(OlgramSettings.version()))
|
||||
|
||||
Репозиторий https://github.com/civsocit/olgram
|
||||
|
||||
Поддержка: @civsocit_feedback_bot
|
||||
|
||||
Версия {OlgramSettings.version()}
|
||||
"""))
|
||||
@dp.message_handler(commands=["chatid"], state="*")
|
||||
@public()
|
||||
async def chat_id(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Команда /chatid
|
||||
"""
|
||||
await message.answer(message.chat.id)
|
||||
|
79
olgram/migrations/custom.py
Normal file
@ -0,0 +1,79 @@
|
||||
"""Наши собственные миграции, которые нельзя описать на языке SQL и с которыми не справится TortoiseORM/Aerich"""
|
||||
|
||||
import aioredis
|
||||
from tortoise import transactions, Tortoise
|
||||
from olgram.settings import TORTOISE_ORM, ServerSettings
|
||||
from olgram.models.models import MetaInfo, Bot
|
||||
import logging
|
||||
|
||||
|
||||
async def upgrade_1():
|
||||
"""Шифруем токены"""
|
||||
meta_info = await MetaInfo.first()
|
||||
if meta_info.version != 0:
|
||||
logging.info("skip")
|
||||
return
|
||||
|
||||
async with transactions.in_transaction():
|
||||
bots = await Bot.all()
|
||||
for bot in bots:
|
||||
bot.token = bot.encrypted_token(bot.token)
|
||||
await bot.save()
|
||||
meta_info.version = 1
|
||||
await meta_info.save()
|
||||
logging.info("done")
|
||||
|
||||
|
||||
async def upgrade_2():
|
||||
"""Отменяем малый TTL для старых сообщений"""
|
||||
meta_info = await MetaInfo.first()
|
||||
if meta_info.version != 1:
|
||||
logging.info("skip")
|
||||
return
|
||||
|
||||
con = await aioredis.create_connection(ServerSettings.redis_path())
|
||||
client = aioredis.Redis(con)
|
||||
|
||||
i, keys = await client.scan()
|
||||
for key in keys:
|
||||
if not key.startswith(b"thread"):
|
||||
await client.pexpire(key, ServerSettings.redis_timeout_ms())
|
||||
|
||||
meta_info.version = 2
|
||||
await meta_info.save()
|
||||
logging.info("done")
|
||||
|
||||
|
||||
async def upgrade_3():
|
||||
"""start_text и second_text должны быть валидными HTML"""
|
||||
import html
|
||||
|
||||
meta_info = await MetaInfo.first()
|
||||
if meta_info.version != 2:
|
||||
logging.info("skip")
|
||||
return
|
||||
|
||||
async with transactions.in_transaction():
|
||||
bots = await Bot.all()
|
||||
for bot in bots:
|
||||
if bot.start_text:
|
||||
bot.start_text = html.escape(bot.start_text)
|
||||
if bot.second_text:
|
||||
bot.second_text = html.escape(bot.second_text)
|
||||
await bot.save(update_fields=["start_text", "second_text"])
|
||||
meta_info.version = 3
|
||||
await meta_info.save()
|
||||
logging.info("done")
|
||||
|
||||
|
||||
# Не забудь добавить миграцию в этот лист!
|
||||
_migrations = [upgrade_1, upgrade_2, upgrade_3]
|
||||
|
||||
|
||||
async def migrate():
|
||||
logging.info("Run custom migrations...")
|
||||
await Tortoise.init(config=TORTOISE_ORM)
|
||||
|
||||
for migration in _migrations:
|
||||
logging.info(f"Migration {migration.__name__}...")
|
||||
await migration()
|
4
olgram/migrations/models/10_20220219201520_update.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "enable_threads" BOOL NOT NULL DEFAULT False;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "enable_threads";
|
4
olgram/migrations/models/11_20220317080443_update.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "enable_additional_info" BOOL NOT NULL DEFAULT False;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "enable_additional_info";
|
10
olgram/migrations/models/12_20220329215535_update.sql
Normal file
@ -0,0 +1,10 @@
|
||||
-- upgrade --
|
||||
CREATE TABLE IF NOT EXISTS "promo" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"code" UUID NOT NULL,
|
||||
"date" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"owner_id" INT REFERENCES "user" ("id") ON DELETE SET NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_promo_code_9b981a" ON "promo" ("code");
|
||||
-- downgrade --
|
||||
DROP TABLE IF EXISTS "promo";
|
4
olgram/migrations/models/13_20220409051838_update.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "enable_olgram_text" BOOL NOT NULL DEFAULT True;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "enable_olgram_text";
|
4
olgram/migrations/models/14_20220801015243_update.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "enable_antiflood" BOOL NOT NULL DEFAULT False;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "enable_antiflood";
|
4
olgram/migrations/models/4_20210926165918_update.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "second_text" TEXT;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "second_text";
|
10
olgram/migrations/models/5_20210926185420_update.sql
Normal file
@ -0,0 +1,10 @@
|
||||
-- upgrade --
|
||||
ALTER TABLE "bot" ALTER COLUMN "token" TYPE VARCHAR(200) USING "token"::VARCHAR(200);
|
||||
CREATE TABLE IF NOT EXISTS "_custom_meta_info" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"version" INT NOT NULL DEFAULT 0
|
||||
);;
|
||||
INSERT INTO _custom_meta_info (id, version) VALUES (0, 0) ON CONFLICT DO NOTHING;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" ALTER COLUMN "token" TYPE VARCHAR(50) USING "token"::VARCHAR(50);
|
||||
DROP TABLE IF EXISTS "_custom_meta_info";
|
11
olgram/migrations/models/6_20220119000000_banned.sql
Normal file
@ -0,0 +1,11 @@
|
||||
-- upgrade --
|
||||
CREATE TABLE IF NOT EXISTS "bot_banned_user" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"telegram_id" BIGINT NOT NULL,
|
||||
"username" VARCHAR(100),
|
||||
"bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_bot_banned__telegra_915aca" ON "bot_banned_user" ("telegram_id");
|
||||
-- downgrade --
|
||||
DROP TABLE IF EXISTS "bot_banned_user";
|
||||
DROP INDEX IF EXISTS "idx_bot_banned__telegra_915aca";
|
7
olgram/migrations/models/7_20220210194635_update.sql
Normal file
@ -0,0 +1,7 @@
|
||||
-- upgrade --
|
||||
CREATE TABLE IF NOT EXISTS "defaultanswer" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE
|
||||
);
|
||||
-- downgrade --
|
||||
DROP TABLE IF EXISTS "defaultanswer";
|
4
olgram/migrations/models/8_20220210201740_update.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- upgrade --
|
||||
ALTER TABLE "defaultanswer" ADD "text" TEXT NOT NULL;
|
||||
-- downgrade --
|
||||
ALTER TABLE "defaultanswer" DROP COLUMN "text";
|
6
olgram/migrations/models/9_20220218211744_update.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "outgoing_messages_count" BIGINT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE "bot" ADD "incoming_messages_count" BIGINT NOT NULL DEFAULT 0;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "outgoing_messages_count";
|
||||
ALTER TABLE "bot" DROP COLUMN "incoming_messages_count";
|
@ -2,18 +2,36 @@ from tortoise.models import Model
|
||||
from tortoise import fields
|
||||
from uuid import uuid4
|
||||
from textwrap import dedent
|
||||
from olgram.settings import DatabaseSettings
|
||||
from locales.locale import _
|
||||
|
||||
|
||||
class MetaInfo(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
version = fields.IntField(default=0)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# Кажется это единственный способ сделать single-instance модель в TortoiseORM :(
|
||||
if "id" in kwargs:
|
||||
kwargs["id"] = 0
|
||||
self.id = 0
|
||||
super(MetaInfo, self).__init__(**kwargs)
|
||||
|
||||
class Meta:
|
||||
table = '_custom_meta_info'
|
||||
|
||||
|
||||
class Bot(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
token = fields.CharField(max_length=50, unique=True)
|
||||
token = fields.CharField(max_length=200, unique=True)
|
||||
owner = fields.ForeignKeyField("models.User", related_name="bots")
|
||||
name = fields.CharField(max_length=33)
|
||||
code = fields.UUIDField(default=uuid4, index=True)
|
||||
start_text = fields.TextField(default=dedent("""
|
||||
start_text = fields.TextField(default=dedent(_("""
|
||||
Здравствуйте!
|
||||
Напишите ваш вопрос и мы ответим вам в ближайшее время.
|
||||
"""))
|
||||
""")))
|
||||
second_text = fields.TextField(null=True, default=None)
|
||||
|
||||
group_chats = fields.ManyToManyField("models.GroupChat", related_name="bots", on_delete=fields.relational.CASCADE,
|
||||
null=True)
|
||||
@ -21,12 +39,33 @@ class Bot(Model):
|
||||
on_delete=fields.relational.CASCADE,
|
||||
null=True)
|
||||
|
||||
incoming_messages_count = fields.BigIntField(default=0)
|
||||
outgoing_messages_count = fields.BigIntField(default=0)
|
||||
|
||||
enable_threads = fields.BooleanField(default=False)
|
||||
enable_additional_info = fields.BooleanField(default=False)
|
||||
enable_olgram_text = fields.BooleanField(default=True)
|
||||
enable_antiflood = fields.BooleanField(default=False)
|
||||
|
||||
def decrypted_token(self):
|
||||
cryptor = DatabaseSettings.cryptor()
|
||||
return cryptor.decrypt(self.token)
|
||||
|
||||
@classmethod
|
||||
def encrypted_token(cls, token: str):
|
||||
cryptor = DatabaseSettings.cryptor()
|
||||
return cryptor.encrypt(token)
|
||||
|
||||
async def super_chat_id(self):
|
||||
group_chat = await self.group_chat
|
||||
if group_chat:
|
||||
return group_chat.chat_id
|
||||
return (await self.owner).telegram_id
|
||||
|
||||
async def is_promo(self):
|
||||
await self.fetch_related("owner")
|
||||
return await self.owner.is_promo()
|
||||
|
||||
class Meta:
|
||||
table = 'bot'
|
||||
|
||||
@ -35,6 +74,10 @@ class User(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
telegram_id = fields.BigIntField(index=True, unique=True)
|
||||
|
||||
async def is_promo(self):
|
||||
await self.fetch_related("promo")
|
||||
return bool(self.promo)
|
||||
|
||||
class Meta:
|
||||
table = 'user'
|
||||
|
||||
@ -46,3 +89,29 @@ class GroupChat(Model):
|
||||
|
||||
class Meta:
|
||||
table = 'group_chat'
|
||||
|
||||
|
||||
class BannedUser(Model):
|
||||
id = fields.BigIntField(pk=True)
|
||||
telegram_id = fields.BigIntField(index=True)
|
||||
username = fields.CharField(max_length=100, default=None, null=True)
|
||||
|
||||
bot = fields.ForeignKeyField("models.Bot", related_name="banned_users", on_delete=fields.relational.CASCADE)
|
||||
|
||||
class Meta:
|
||||
table = "bot_banned_user"
|
||||
|
||||
|
||||
class DefaultAnswer(Model):
|
||||
id = fields.BigIntField(pk=True)
|
||||
bot = fields.ForeignKeyField("models.Bot", related_name="answers", on_delete=fields.relational.CASCADE)
|
||||
text = fields.TextField()
|
||||
|
||||
|
||||
class Promo(Model):
|
||||
id = fields.BigIntField(pk=True)
|
||||
code = fields.UUIDField(default=uuid4, index=True)
|
||||
date = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
owner = fields.ForeignKeyField("models.User", related_name="promo", on_delete=fields.relational.SET_NULL,
|
||||
null=True, default=None)
|
||||
|
@ -1,18 +1,25 @@
|
||||
from dotenv import load_dotenv
|
||||
from abc import ABC
|
||||
import os
|
||||
import logging
|
||||
from functools import lru_cache
|
||||
from datetime import timedelta
|
||||
import typing as ty
|
||||
from olgram.utils.crypto import Cryptor
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
# TODO: рефакторинг, использовать какой-нибудь lazy-config вместо своих костылей
|
||||
|
||||
class AbstractSettings(ABC):
|
||||
@classmethod
|
||||
def _get_env(cls, parameter: str, allow_none: bool = False) -> str:
|
||||
parameter = os.getenv(parameter, None)
|
||||
if not parameter and not allow_none:
|
||||
parameter_v = os.getenv(parameter, None)
|
||||
if not parameter_v and not allow_none:
|
||||
raise ValueError(f"{parameter} not defined in ENV")
|
||||
return parameter
|
||||
return parameter_v
|
||||
|
||||
|
||||
class OlgramSettings(AbstractSettings):
|
||||
@ -22,11 +29,31 @@ class OlgramSettings(AbstractSettings):
|
||||
Максимальное количество ботов у одного пользователя
|
||||
:return: int
|
||||
"""
|
||||
return 5
|
||||
return 10
|
||||
|
||||
@classmethod
|
||||
def max_bots_per_user_promo(cls) -> int:
|
||||
"""
|
||||
Максимальное количество ботов у одного пользователя с промо-доступом
|
||||
:return: int
|
||||
"""
|
||||
return 25
|
||||
|
||||
@classmethod
|
||||
def version(cls):
|
||||
return "0.0.3"
|
||||
return "0.5.0"
|
||||
|
||||
@classmethod
|
||||
@lru_cache
|
||||
def admin_ids(cls):
|
||||
_ids = cls._get_env("ADMIN_ID", True)
|
||||
return set(map(int, _ids.split(","))) if _ids else None
|
||||
|
||||
@classmethod
|
||||
@lru_cache
|
||||
def supervisor_id(cls):
|
||||
_id = cls._get_env("SUPERVISOR_ID", True)
|
||||
return int(_id) if _id else None
|
||||
|
||||
|
||||
class ServerSettings(AbstractSettings):
|
||||
@ -38,10 +65,6 @@ class ServerSettings(AbstractSettings):
|
||||
def hook_port(cls) -> int:
|
||||
return int(cls._get_env("WEBHOOK_PORT"))
|
||||
|
||||
@classmethod
|
||||
def app_host(cls) -> str:
|
||||
return "olgram"
|
||||
|
||||
@classmethod
|
||||
def app_port(cls) -> int:
|
||||
return 80
|
||||
@ -71,9 +94,24 @@ class ServerSettings(AbstractSettings):
|
||||
def append_text(cls) -> str:
|
||||
return "\n\nЭтот бот создан с помощью @OlgramBot"
|
||||
|
||||
@classmethod
|
||||
@lru_cache
|
||||
def redis_timeout_ms(cls) -> ty.Optional[int]:
|
||||
return int(timedelta(days=180).total_seconds() * 1000.0)
|
||||
|
||||
@classmethod
|
||||
@lru_cache
|
||||
def thread_timeout_ms(cls) -> int:
|
||||
return int(timedelta(days=1).total_seconds() * 1000.0)
|
||||
|
||||
|
||||
logging.basicConfig(level=os.environ.get("LOGLEVEL") or "WARNING",
|
||||
format='%(asctime)s %(levelname)-8s %(message)s')
|
||||
|
||||
|
||||
class BotSettings(AbstractSettings):
|
||||
@classmethod
|
||||
@lru_cache
|
||||
def token(cls) -> str:
|
||||
"""
|
||||
Токен olgram бота
|
||||
@ -81,6 +119,14 @@ class BotSettings(AbstractSettings):
|
||||
"""
|
||||
return cls._get_env("BOT_TOKEN")
|
||||
|
||||
@classmethod
|
||||
def language(cls) -> str:
|
||||
"""
|
||||
Язык
|
||||
"""
|
||||
lang = cls._get_env("O_LANG", allow_none=True)
|
||||
return lang.lower() if lang else "ru"
|
||||
|
||||
|
||||
class DatabaseSettings(AbstractSettings):
|
||||
@classmethod
|
||||
@ -99,6 +145,12 @@ class DatabaseSettings(AbstractSettings):
|
||||
def host(cls) -> str:
|
||||
return cls._get_env("POSTGRES_HOST")
|
||||
|
||||
@classmethod
|
||||
@lru_cache
|
||||
def cryptor(cls) -> Cryptor:
|
||||
password = cls._get_env("TOKEN_ENCRYPTION_KEY")
|
||||
return Cryptor(password)
|
||||
|
||||
|
||||
TORTOISE_ORM = {
|
||||
"connections": {"default": f'postgres://{DatabaseSettings.user()}:{DatabaseSettings.password()}'
|
||||
|
16
olgram/utils/crypto.py
Normal file
@ -0,0 +1,16 @@
|
||||
import base64
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
|
||||
class Cryptor:
|
||||
def __init__(self, password: str):
|
||||
password = password.rjust(32)[:32]
|
||||
self._cipher = AES.new(password.encode("utf-8"), AES.MODE_ECB)
|
||||
|
||||
def encrypt(self, data: str) -> str:
|
||||
if data.startswith(" "):
|
||||
raise ValueError("Data should not start with space!")
|
||||
return base64.b64encode(self._cipher.encrypt(data.encode("utf-8").rjust(64))).decode("utf-8")
|
||||
|
||||
def decrypt(self, data: str) -> str:
|
||||
return self._cipher.decrypt(base64.b64decode(data.encode("utf-8"))).decode("utf-8").lstrip()
|
@ -22,8 +22,11 @@ async def edit_or_create(call: CallbackQuery, message: str,
|
||||
parse_mode=parse_mode)
|
||||
|
||||
|
||||
def button_text_limit(data: str) -> str:
|
||||
max_len = 30
|
||||
def wrap(data: str, max_len: int) -> str:
|
||||
if len(data) > max_len:
|
||||
data = data[:max_len-4] + "..."
|
||||
return data
|
||||
|
||||
|
||||
def button_text_limit(data: str) -> str:
|
||||
return wrap(data, 30)
|
||||
|
54
olgram/utils/permissions.py
Normal file
@ -0,0 +1,54 @@
|
||||
import aiogram.types as types
|
||||
from aiogram.dispatcher.handler import CancelHandler, current_handler
|
||||
from aiogram.dispatcher.middlewares import BaseMiddleware
|
||||
import typing as ty
|
||||
from locales.locale import _
|
||||
|
||||
|
||||
def public():
|
||||
"""
|
||||
Хендлеры с этим декоратором будут обрабатываться даже если пользователь не является владельцем бота
|
||||
(например, команда /help)
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
setattr(func, "access_public", True)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class AccessMiddleware(BaseMiddleware):
|
||||
def __init__(self, access_chat_ids: ty.Iterable[int]):
|
||||
self._access_chat_ids = access_chat_ids
|
||||
super(AccessMiddleware, self).__init__()
|
||||
|
||||
@classmethod
|
||||
def _is_public_command(cls) -> bool:
|
||||
handler = current_handler.get()
|
||||
return handler and getattr(handler, "access_public", False)
|
||||
|
||||
async def on_process_message(self, message: types.Message, data: dict):
|
||||
admin_ids = self._access_chat_ids
|
||||
if not admin_ids:
|
||||
return # Администраторы бота вообще не указаны
|
||||
|
||||
if self._is_public_command(): # Эта команда разрешена всем пользователям
|
||||
return
|
||||
|
||||
if message.chat.id not in admin_ids:
|
||||
await message.answer(_("Владелец бота ограничил доступ к этому функционалу 😞"))
|
||||
raise CancelHandler()
|
||||
|
||||
async def on_process_callback_query(self, call: types.CallbackQuery, data: dict):
|
||||
admin_ids = self._access_chat_ids
|
||||
if not admin_ids:
|
||||
return # Администраторы бота вообще не указаны
|
||||
|
||||
if self._is_public_command(): # Эта команда разрешена всем пользователям
|
||||
return
|
||||
|
||||
if call.message.chat.id not in admin_ids:
|
||||
await call.answer(_("Владелец бота ограничил доступ к этому функционалу😞"))
|
||||
raise CancelHandler()
|
898
poetry.lock
generated
Normal file
@ -0,0 +1,898 @@
|
||||
[[package]]
|
||||
name = "aerich"
|
||||
version = "0.5.7"
|
||||
description = "A database migrations tool for Tortoise ORM."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7,<4.0"
|
||||
|
||||
[package.dependencies]
|
||||
click = "*"
|
||||
ddlparse = "*"
|
||||
dictdiffer = "*"
|
||||
pydantic = "*"
|
||||
tortoise-orm = "*"
|
||||
|
||||
[package.extras]
|
||||
aiomysql = ["aiomysql"]
|
||||
asyncpg = ["asyncpg"]
|
||||
|
||||
[[package]]
|
||||
name = "aiocache"
|
||||
version = "0.11.1"
|
||||
description = "multi backend asyncio cache"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["asynctest (>=0.11.0)", "codecov", "coverage", "flake8", "ipdb", "marshmallow", "pystache", "pytest", "pytest-asyncio", "pytest-mock", "sphinx", "sphinx-autobuild", "sphinx-rtd-theme", "black"]
|
||||
memcached = ["aiomcache (>=0.5.2)"]
|
||||
msgpack = ["msgpack (>=0.5.5)"]
|
||||
redis = ["aioredis (>=0.3.3)", "aioredis (>=1.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "aiogram"
|
||||
version = "2.13"
|
||||
description = "Is a pretty simple and fully asynchronous framework for Telegram Bot API"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
aiohttp = ">=3.7.2,<4.0.0"
|
||||
Babel = ">=2.8.0"
|
||||
certifi = ">=2020.6.20"
|
||||
|
||||
[package.extras]
|
||||
fast = ["uvloop (>=0.14.0,<0.15.0)", "ujson (>=1.35)"]
|
||||
proxy = ["aiohttp-socks (>=0.5.3,<0.6.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.8.1"
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
aiosignal = ">=1.1.2"
|
||||
async-timeout = ">=4.0.0a3,<5.0"
|
||||
attrs = ">=17.3.0"
|
||||
charset-normalizer = ">=2.0,<3.0"
|
||||
frozenlist = ">=1.1.1"
|
||||
multidict = ">=4.5,<7.0"
|
||||
yarl = ">=1.0,<2.0"
|
||||
|
||||
[package.extras]
|
||||
speedups = ["aiodns", "brotli", "cchardet"]
|
||||
|
||||
[[package]]
|
||||
name = "aioredis"
|
||||
version = "1.3.0"
|
||||
description = "asyncio (PEP 3156) Redis support"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
async-timeout = "*"
|
||||
hiredis = "*"
|
||||
|
||||
[[package]]
|
||||
name = "aiosignal"
|
||||
version = "1.2.0"
|
||||
description = "aiosignal: a list of registered asynchronous callbacks"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
frozenlist = ">=1.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "aiosqlite"
|
||||
version = "0.17.0"
|
||||
description = "asyncio bridge to the standard sqlite3 module"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
typing_extensions = ">=3.7.2"
|
||||
|
||||
[[package]]
|
||||
name = "async-timeout"
|
||||
version = "4.0.2"
|
||||
description = "Timeout context manager for asyncio programs"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "asyncpg"
|
||||
version = "0.25.0"
|
||||
description = "An asyncio PostgreSQL driver"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "pycodestyle (>=2.7.0,<2.8.0)", "flake8 (>=3.9.2,<3.10.0)", "uvloop (>=0.15.3)"]
|
||||
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
|
||||
test = ["pycodestyle (>=2.7.0,<2.8.0)", "flake8 (>=3.9.2,<3.10.0)", "uvloop (>=0.15.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "21.4.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
|
||||
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.10.3"
|
||||
description = "Internationalization utilities"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pytz = ">=2015.7"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.6.15"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.1.0"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.0"
|
||||
|
||||
[package.extras]
|
||||
unicode_backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.5"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "ddlparse"
|
||||
version = "1.10.0"
|
||||
description = "DDL parase and Convert to BigQuery JSON schema"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = "*"
|
||||
|
||||
[[package]]
|
||||
name = "dictdiffer"
|
||||
version = "0.9.0"
|
||||
description = "Dictdiffer is a library that helps you to diff and patch dictionaries."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
all = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)", "check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "sphinx (>=3)", "tox (>=3.7.0)", "numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "pytest (==5.4.3)", "pytest-pycodestyle (>=2)", "pytest-pydocstyle (>=2)", "pytest (>=6)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2.2.0)", "numpy (>=1.20.0)"]
|
||||
docs = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)"]
|
||||
numpy = ["numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)"]
|
||||
tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "sphinx (>=3)", "tox (>=3.7.0)", "pytest (==5.4.3)", "pytest-pycodestyle (>=2)", "pytest-pydocstyle (>=2)", "pytest (>=6)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2.2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "flake8"
|
||||
version = "4.0.1"
|
||||
description = "the modular source code checker: pep8 pyflakes and co"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
mccabe = ">=0.6.0,<0.7.0"
|
||||
pycodestyle = ">=2.8.0,<2.9.0"
|
||||
pyflakes = ">=2.4.0,<2.5.0"
|
||||
|
||||
[[package]]
|
||||
name = "frozenlist"
|
||||
version = "1.3.0"
|
||||
description = "A list-like structure which implements collections.abc.MutableSequence"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "hiredis"
|
||||
version = "2.0.0"
|
||||
description = "Python wrapper for hiredis"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.3"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "iso8601"
|
||||
version = "0.1.16"
|
||||
description = "Simple module to parse ISO 8601 dates"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "mccabe"
|
||||
version = "0.6.1"
|
||||
description = "McCabe checker, plugin for flake8"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "multidict"
|
||||
version = "6.0.2"
|
||||
description = "multidict implementation"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "pycodestyle"
|
||||
version = "2.8.0"
|
||||
description = "Python style guide checker"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pycrypto"
|
||||
version = "2.6.1"
|
||||
description = "Cryptographic modules for Python."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.9.1"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=3.7.4.3"
|
||||
|
||||
[package.extras]
|
||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyflakes"
|
||||
version = "2.4.0"
|
||||
description = "passive checker of Python programs"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.9"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.8"
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["railroad-diagrams", "jinja2"]
|
||||
|
||||
[[package]]
|
||||
name = "pypika-tortoise"
|
||||
version = "0.1.5"
|
||||
description = "Forked from pypika and streamline just for tortoise-orm"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7,<4.0"
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "0.19.2"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-gettext"
|
||||
version = "4.0"
|
||||
description = "Python Gettext po to mo file compiler."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2022.1"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "tortoise-orm"
|
||||
version = "0.18.1"
|
||||
description = "Easy async ORM for python, built with relations in mind"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7,<4.0"
|
||||
|
||||
[package.dependencies]
|
||||
aiosqlite = ">=0.16.0,<0.18.0"
|
||||
asyncpg = {version = "*", optional = true, markers = "extra == \"asyncpg\""}
|
||||
iso8601 = ">=0.1.13,<0.2.0"
|
||||
pypika-tortoise = ">=0.1.3,<0.2.0"
|
||||
pytz = "*"
|
||||
|
||||
[package.extras]
|
||||
aiomysql = ["aiomysql"]
|
||||
asyncmy = ["asyncmy"]
|
||||
asyncpg = ["asyncpg"]
|
||||
accel = ["ciso8601", "orjson", "uvloop"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.2.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "yarl"
|
||||
version = "1.7.2"
|
||||
description = "Yet another URL library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.0"
|
||||
multidict = ">=4.0"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "9151864c69f349148a36cc7551bb54fd240229eb2533e92b82a0f85a251a56f3"
|
||||
|
||||
[metadata.files]
|
||||
aerich = [
|
||||
{file = "aerich-0.5.7-py3-none-any.whl", hash = "sha256:0684eb3d631c7c6e14caf2b2c3b9dad1c15ce8ade5771773c015a302f54ff4f6"},
|
||||
{file = "aerich-0.5.7.tar.gz", hash = "sha256:f9ef8796f7a13ba9965eda0aa6840033bbd42b2e4e52c24d8f0dbdb85e4a5187"},
|
||||
]
|
||||
aiocache = [
|
||||
{file = "aiocache-0.11.1-py2.py3-none-any.whl", hash = "sha256:e55c7caaa5753794fd301c3a2e592737fa1d036db9f8d04ae154facdfb48a157"},
|
||||
{file = "aiocache-0.11.1.tar.gz", hash = "sha256:f2ebe0b05cec45782e7b5ea0bb74640f157dd4bb1028b4565364dda9fe33be7f"},
|
||||
]
|
||||
aiogram = [
|
||||
{file = "aiogram-2.13-py3-none-any.whl", hash = "sha256:e1923ad789bb8d90a7b1edc5fce7dc33df982d1fedc6528c65cc38d2d88f1ae0"},
|
||||
{file = "aiogram-2.13.tar.gz", hash = "sha256:f656f57580fd8e1ad0f8fe645f59cf3ad40e9899050dd6ac999a8f0cdbf1b116"},
|
||||
]
|
||||
aiohttp = [
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"},
|
||||
{file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"},
|
||||
]
|
||||
aioredis = [
|
||||
{file = "aioredis-1.3.0-py3-none-any.whl", hash = "sha256:71302cebeb7add86f1fe660b469068760ca4364504e75ee83dd6f6b7118bfe28"},
|
||||
{file = "aioredis-1.3.0.tar.gz", hash = "sha256:86da2748fb0652625a8346f413167f078ec72bdc76e217db7e605a059cd56e86"},
|
||||
]
|
||||
aiosignal = [
|
||||
{file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"},
|
||||
{file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"},
|
||||
]
|
||||
aiosqlite = [
|
||||
{file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"},
|
||||
{file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"},
|
||||
]
|
||||
async-timeout = [
|
||||
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
|
||||
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
|
||||
]
|
||||
asyncpg = [
|
||||
{file = "asyncpg-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf5e3408a14a17d480f36ebaf0401a12ff6ae5457fdf45e4e2775c51cc9517d3"},
|
||||
{file = "asyncpg-0.25.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bc197fc4aca2fd24f60241057998124012469d2e414aed3f992579db0c88e3a"},
|
||||
{file = "asyncpg-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a70783f6ffa34cc7dd2de20a873181414a34fd35a4a208a1f1a7f9f695e4ec4"},
|
||||
{file = "asyncpg-0.25.0-cp310-cp310-win32.whl", hash = "sha256:43cde84e996a3afe75f325a68300093425c2f47d340c0fc8912765cf24a1c095"},
|
||||
{file = "asyncpg-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:56d88d7ef4341412cd9c68efba323a4519c916979ba91b95d4c08799d2ff0c09"},
|
||||
{file = "asyncpg-0.25.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a84d30e6f850bac0876990bcd207362778e2208df0bee8be8da9f1558255e634"},
|
||||
{file = "asyncpg-0.25.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:beaecc52ad39614f6ca2e48c3ca15d56e24a2c15cbfdcb764a4320cc45f02fd5"},
|
||||
{file = "asyncpg-0.25.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:6f8f5fc975246eda83da8031a14004b9197f510c41511018e7b1bedde6968e92"},
|
||||
{file = "asyncpg-0.25.0-cp36-cp36m-win32.whl", hash = "sha256:ddb4c3263a8d63dcde3d2c4ac1c25206bfeb31fa83bd70fd539e10f87739dee4"},
|
||||
{file = "asyncpg-0.25.0-cp36-cp36m-win_amd64.whl", hash = "sha256:bf6dc9b55b9113f39eaa2057337ce3f9ef7de99a053b8a16360395ce588925cd"},
|
||||
{file = "asyncpg-0.25.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:acb311722352152936e58a8ee3c5b8e791b24e84cd7d777c414ff05b3530ca68"},
|
||||
{file = "asyncpg-0.25.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a61fb196ce4dae2f2fa26eb20a778db21bbee484d2e798cb3cc988de13bdd1b"},
|
||||
{file = "asyncpg-0.25.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2633331cbc8429030b4f20f712f8d0fbba57fa8555ee9b2f45f981b81328b256"},
|
||||
{file = "asyncpg-0.25.0-cp37-cp37m-win32.whl", hash = "sha256:863d36eba4a7caa853fd7d83fad5fd5306f050cc2fe6e54fbe10cdb30420e5e9"},
|
||||
{file = "asyncpg-0.25.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fe471ccd915b739ca65e2e4dbd92a11b44a5b37f2e38f70827a1c147dafe0fa8"},
|
||||
{file = "asyncpg-0.25.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:72a1e12ea0cf7c1e02794b697e3ca967b2360eaa2ce5d4bfdd8604ec2d6b774b"},
|
||||
{file = "asyncpg-0.25.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4327f691b1bdb222df27841938b3e04c14068166b3a97491bec2cb982f49f03e"},
|
||||
{file = "asyncpg-0.25.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:739bbd7f89a2b2f6bc44cb8bf967dab12c5bc714fcbe96e68d512be45ecdf962"},
|
||||
{file = "asyncpg-0.25.0-cp38-cp38-win32.whl", hash = "sha256:18d49e2d93a7139a2fdbd113e320cc47075049997268a61bfbe0dde680c55471"},
|
||||
{file = "asyncpg-0.25.0-cp38-cp38-win_amd64.whl", hash = "sha256:191fe6341385b7fdea7dbdcf47fd6db3fd198827dcc1f2b228476d13c05a03c6"},
|
||||
{file = "asyncpg-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fab7f1b2c29e187dd8781fce896249500cf055b63471ad66332e537e9b5f7e"},
|
||||
{file = "asyncpg-0.25.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a738f1b2876f30d710d3dc1e7858160a0afe1603ba16bf5f391f5316eb0ed855"},
|
||||
{file = "asyncpg-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4105f57ad1e8fbc8b1e535d8fcefa6ce6c71081228f08680c6dea24384ff0e"},
|
||||
{file = "asyncpg-0.25.0-cp39-cp39-win32.whl", hash = "sha256:f55918ded7b85723a5eaeb34e86e7b9280d4474be67df853ab5a7fa0cc7c6bf2"},
|
||||
{file = "asyncpg-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:649e2966d98cc48d0646d9a4e29abecd8b59d38d55c256d5c857f6b27b7407ac"},
|
||||
{file = "asyncpg-0.25.0.tar.gz", hash = "sha256:63f8e6a69733b285497c2855464a34de657f2cccd25aeaeeb5071872e9382540"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
|
||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||
]
|
||||
babel = [
|
||||
{file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"},
|
||||
{file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
|
||||
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||
]
|
||||
ddlparse = [
|
||||
{file = "ddlparse-1.10.0-py3-none-any.whl", hash = "sha256:71761b3457c8720853af3aeef266e2da1b6edef50936969492d586d7046a2ac2"},
|
||||
{file = "ddlparse-1.10.0.tar.gz", hash = "sha256:6418681baa848eb01251ab79eb3d0ad7e140e6ab1deaae5a019353ddb3a908da"},
|
||||
]
|
||||
dictdiffer = [
|
||||
{file = "dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595"},
|
||||
{file = "dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578"},
|
||||
]
|
||||
flake8 = [
|
||||
{file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
|
||||
{file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
|
||||
]
|
||||
frozenlist = [
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-win32.whl", hash = "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d"},
|
||||
{file = "frozenlist-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468"},
|
||||
{file = "frozenlist-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-win32.whl", hash = "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07"},
|
||||
{file = "frozenlist-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-win32.whl", hash = "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08"},
|
||||
{file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"},
|
||||
{file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"},
|
||||
]
|
||||
hiredis = [
|
||||
{file = "hiredis-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048"},
|
||||
{file = "hiredis-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26"},
|
||||
{file = "hiredis-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea"},
|
||||
{file = "hiredis-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99"},
|
||||
{file = "hiredis-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05"},
|
||||
{file = "hiredis-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a"},
|
||||
{file = "hiredis-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63"},
|
||||
{file = "hiredis-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6"},
|
||||
{file = "hiredis-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485"},
|
||||
{file = "hiredis-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a"},
|
||||
{file = "hiredis-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc"},
|
||||
{file = "hiredis-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579"},
|
||||
{file = "hiredis-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e"},
|
||||
{file = "hiredis-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79"},
|
||||
{file = "hiredis-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc"},
|
||||
{file = "hiredis-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"},
|
||||
{file = "hiredis-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb"},
|
||||
{file = "hiredis-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5"},
|
||||
{file = "hiredis-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298"},
|
||||
{file = "hiredis-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d"},
|
||||
{file = "hiredis-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db"},
|
||||
{file = "hiredis-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048"},
|
||||
{file = "hiredis-2.0.0-cp38-cp38-win32.whl", hash = "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426"},
|
||||
{file = "hiredis-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581"},
|
||||
{file = "hiredis-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5"},
|
||||
{file = "hiredis-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e"},
|
||||
{file = "hiredis-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce"},
|
||||
{file = "hiredis-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443"},
|
||||
{file = "hiredis-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0"},
|
||||
{file = "hiredis-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e"},
|
||||
{file = "hiredis-2.0.0-cp39-cp39-win32.whl", hash = "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d"},
|
||||
{file = "hiredis-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9"},
|
||||
{file = "hiredis-2.0.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54"},
|
||||
{file = "hiredis-2.0.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27"},
|
||||
{file = "hiredis-2.0.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d"},
|
||||
{file = "hiredis-2.0.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163"},
|
||||
{file = "hiredis-2.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a"},
|
||||
{file = "hiredis-2.0.0-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87"},
|
||||
{file = "hiredis-2.0.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41"},
|
||||
{file = "hiredis-2.0.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0"},
|
||||
{file = "hiredis-2.0.0.tar.gz", hash = "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||
]
|
||||
iso8601 = [
|
||||
{file = "iso8601-0.1.16-py2.py3-none-any.whl", hash = "sha256:906714829fedbc89955d52806c903f2332e3948ed94e31e85037f9e0226b8376"},
|
||||
{file = "iso8601-0.1.16.tar.gz", hash = "sha256:36532f77cc800594e8f16641edae7f1baf7932f05d8e508545b95fc53c6dc85b"},
|
||||
]
|
||||
mccabe = [
|
||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||
]
|
||||
multidict = [
|
||||
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"},
|
||||
{file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"},
|
||||
{file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"},
|
||||
{file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"},
|
||||
{file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"},
|
||||
]
|
||||
pycodestyle = [
|
||||
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
|
||||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
||||
]
|
||||
pycrypto = [
|
||||
{file = "pycrypto-2.6.1.tar.gz", hash = "sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c"},
|
||||
]
|
||||
pydantic = [
|
||||
{file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320c64dd876e45254bdd350f0179da737463eea41c43bacbee9d8c9d1021f11"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11951b404e08b01b151222a1cb1a9f0a860a8153ce8334149ab9199cd198131"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8bc541a405423ce0e51c19f637050acdbdf8feca34150e0d17f675e72d119580"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e565a785233c2d03724c4dc55464559639b1ba9ecf091288dd47ad9c629433bd"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4a88dcd6ff8fd47c18b3a3709a89adb39a6373f4482e04c1b765045c7e282fd"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:447d5521575f18e18240906beadc58551e97ec98142266e521c34968c76c8761"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985ceb5d0a86fcaa61e45781e567a59baa0da292d5ed2e490d612d0de5796918"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059b6c1795170809103a1538255883e1983e5b831faea6558ef873d4955b4a74"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d12f96b5b64bec3f43c8e82b4aab7599d0157f11c798c9f9c528a72b9e0b339a"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ae72f8098acb368d877b210ebe02ba12585e77bd0db78ac04a1ee9b9f5dd2166"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:79b485767c13788ee314669008d01f9ef3bc05db9ea3298f6a50d3ef596a154b"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:494f7c8537f0c02b740c229af4cb47c0d39840b829ecdcfc93d91dcbb0779892"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:969dd06110cb780da01336b281f53e2e7eb3a482831df441fb65dd30403f4608"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:177071dfc0df6248fd22b43036f936cfe2508077a72af0933d0c1fa269b18537"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9bcf8b6e011be08fb729d110f3e22e654a50f8a826b0575c7196616780683380"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a955260d47f03df08acf45689bd163ed9df82c0e0124beb4251b1290fa7ae728"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ce157d979f742a915b75f792dbd6aa63b8eccaf46a1005ba03aa8a986bde34a"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0bf07cab5b279859c253d26a9194a8906e6f4a210063b84b433cf90a569de0c1"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d93d4e95eacd313d2c765ebe40d49ca9dd2ed90e5b37d0d421c597af830c195"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1542636a39c4892c4f4fa6270696902acb186a9aaeac6f6cf92ce6ae2e88564b"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a9af62e9b5b9bc67b2a195ebc2c2662fdf498a822d62f902bf27cccb52dbbf49"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9f659a5ee95c8baa2436d392267988fd0f43eb774e5eb8739252e5a7e9cf07e0"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b83ba3825bc91dfa989d4eed76865e71aea3a6ca1388b59fc801ee04c4d8d0d6"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd8fecbad028cd89d04a46688d2fcc14423e8a196d5b0a5c65105664901f810"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02eefd7087268b711a3ff4db528e9916ac9aa18616da7bca69c1871d0b7a091f"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb57ba90929bac0b6cc2af2373893d80ac559adda6933e562dcfb375029acee"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4ce9ae9e91f46c344bec3b03d6ee9612802682c1551aaf627ad24045ce090761"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ccb318bf0c9ab97fc04c10c37683d9eea952ed526707fabf9ac5ae59b701fd"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b6760b08b7c395975d893e0b814a11cf011ebb24f7d869e7118f5a339a82e1"},
|
||||
{file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"},
|
||||
{file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"},
|
||||
]
|
||||
pyflakes = [
|
||||
{file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
|
||||
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||
]
|
||||
pypika-tortoise = [
|
||||
{file = "pypika-tortoise-0.1.5.tar.gz", hash = "sha256:2e2f747bfc645a25e097485651278cd7d920b709686ce54e43387ba1c9294048"},
|
||||
{file = "pypika_tortoise-0.1.5-py3-none-any.whl", hash = "sha256:23d993558e3005ac7f7f2865d9add3d8168097f45246f8844fa46d6682a99d90"},
|
||||
]
|
||||
python-dotenv = [
|
||||
{file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"},
|
||||
{file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"},
|
||||
]
|
||||
python-gettext = [
|
||||
{file = "python-gettext-4.0.tar.gz", hash = "sha256:626b501a51ac892fc3460cf550e60dca121f544eaa46eb69c90ce4682fc7ec02"},
|
||||
]
|
||||
pytz = [
|
||||
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
|
||||
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
|
||||
]
|
||||
tortoise-orm = [
|
||||
{file = "tortoise-orm-0.18.1.tar.gz", hash = "sha256:537361ce2d0829741afd43afd9bc9413a314a176cb58747d88047c20ccc01db1"},
|
||||
{file = "tortoise_orm-0.18.1-py3-none-any.whl", hash = "sha256:edc9f3b49635b1dd74f73de38f54e031377e4f02b3698322502047f2e031af8b"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
|
||||
{file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
|
||||
]
|
||||
yarl = [
|
||||
{file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-win32.whl", hash = "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c"},
|
||||
{file = "yarl-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-win32.whl", hash = "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1"},
|
||||
{file = "yarl-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-win32.whl", hash = "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1"},
|
||||
{file = "yarl-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-win32.whl", hash = "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef"},
|
||||
{file = "yarl-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-win32.whl", hash = "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d"},
|
||||
{file = "yarl-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58"},
|
||||
{file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"},
|
||||
]
|
25
pyproject.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[tool.poetry]
|
||||
name = "olgram"
|
||||
version = "0.2.0"
|
||||
description = ""
|
||||
authors = ["Civ Soc <feedback@civsoc.it>"]
|
||||
license = "CC0"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
aiogram = "2.13"
|
||||
python-dotenv = "^0.19.2"
|
||||
aiocache = "^0.11.1"
|
||||
aiohttp = "^3.8.1"
|
||||
pycrypto = "^2.6.1"
|
||||
aioredis = "1.3"
|
||||
aerich = "0.5.x"
|
||||
tortoise-orm = {extras = ["asyncpg"], version = "^0.18.1"}
|
||||
python-gettext = "^4.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
flake8 = "^4.0.1"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
1
refresh_lang.sh
Normal file
@ -0,0 +1 @@
|
||||
/usr/lib/python3.10/Tools/i18n/pygettext.py -d chinese -o locales/olgram.pot olgram/ server/
|
@ -1,7 +0,0 @@
|
||||
aiogram~=2.13
|
||||
tortoise-orm[asyncpg]
|
||||
aerich==0.5.4
|
||||
python-dotenv~=0.17.1
|
||||
aioredis==1.3.1
|
||||
aiocache
|
||||
aiohttp
|
288
server/custom.py
@ -7,20 +7,28 @@ from contextvars import ContextVar
|
||||
from aiohttp.web_exceptions import HTTPNotFound
|
||||
from aioredis.commands import create_redis_pool
|
||||
from aioredis import Redis
|
||||
from tortoise.expressions import F
|
||||
import logging
|
||||
import typing as ty
|
||||
from olgram.settings import ServerSettings
|
||||
from olgram.models.models import Bot, GroupChat
|
||||
from olgram.models.models import Bot, GroupChat, BannedUser
|
||||
from locales.locale import _, translators
|
||||
from server.inlines import inline_handler
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
_logger = logging.getLogger(__name__)
|
||||
_logger.setLevel(logging.INFO)
|
||||
|
||||
db_bot_instance: ContextVar[Bot] = ContextVar('db_bot_instance')
|
||||
|
||||
_redis: ty.Optional[Redis] = None
|
||||
|
||||
|
||||
def _get_translator(message: types.Message) -> ty.Callable:
|
||||
if not message.from_user.locale:
|
||||
return _
|
||||
return translators.get(message.from_user.locale.language, _)
|
||||
|
||||
|
||||
async def init_redis():
|
||||
global _redis
|
||||
_redis = await create_redis_pool(ServerSettings.redis_path())
|
||||
@ -30,41 +38,207 @@ def _message_unique_id(bot_id: int, message_id: int) -> str:
|
||||
return f"{bot_id}_{message_id}"
|
||||
|
||||
|
||||
async def message_handler(message, *args, **kwargs):
|
||||
_logger.info("message handler")
|
||||
def _thread_uniqie_id(bot_id: int, chat_id: int) -> str:
|
||||
return f"thread_{bot_id}_{chat_id}"
|
||||
|
||||
|
||||
def _last_message_uid(bot_id: int, chat_id: int) -> str:
|
||||
return f"lm_{bot_id}_{chat_id}"
|
||||
|
||||
|
||||
def _antiflood_marker_uid(bot_id: int, chat_id: int) -> str:
|
||||
return f"af_{bot_id}_{chat_id}"
|
||||
|
||||
|
||||
def _on_security_policy(message: types.Message, bot):
|
||||
_ = _get_translator(message)
|
||||
text = _("<b>Политика конфиденциальности</b>\n\n"
|
||||
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При отправке сообщения (кроме команд "
|
||||
"/start и /security_policy) ваш идентификатор пользователя записывается в кеш на некоторое время и потом "
|
||||
"удаляется из кеша. Этот идентификатор используется только для общения с оператором; боты Olgram "
|
||||
"не делают массовых рассылок.\n\n")
|
||||
if bot.enable_additional_info:
|
||||
text += _("При отправке сообщения (кроме команд /start и /security_policy) оператор <b>видит</b> ваши имя "
|
||||
"пользователя, @username и идентификатор пользователя в силу настроек, которые оператор указал при "
|
||||
"создании бота.")
|
||||
else:
|
||||
text += _("В зависимости от ваших настроек конфиденциальности Telegram, оператор может видеть ваш username, "
|
||||
"имя пользователя и другую информацию.")
|
||||
|
||||
return SendMessage(chat_id=message.chat.id,
|
||||
text=text,
|
||||
parse_mode="HTML")
|
||||
|
||||
|
||||
async def send_user_message(message: types.Message, super_chat_id: int, bot):
|
||||
"""Переслать сообщение от пользователя, добавлять к нему user info при необходимости"""
|
||||
if bot.enable_additional_info:
|
||||
user_info = _("Сообщение от пользователя ")
|
||||
user_info += message.from_user.full_name
|
||||
if message.from_user.username:
|
||||
user_info += " | @" + message.from_user.username
|
||||
user_info += f" | #ID{message.from_user.id}"
|
||||
|
||||
# Добавлять информацию в конец текста
|
||||
if message.content_type == types.ContentType.TEXT and len(message.text) + len(user_info) < 4093: # noqa:E721
|
||||
new_message = await message.bot.send_message(super_chat_id, message.text + "\n\n" + user_info)
|
||||
else: # Не добавлять информацию в конец текста, информация отдельным сообщением
|
||||
new_message = await message.bot.send_message(super_chat_id, text=user_info)
|
||||
new_message_2 = await message.copy_to(super_chat_id, reply_to_message_id=new_message.message_id)
|
||||
await _redis.set(_message_unique_id(bot.pk, new_message_2.message_id), message.chat.id,
|
||||
pexpire=ServerSettings.redis_timeout_ms())
|
||||
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id,
|
||||
pexpire=ServerSettings.redis_timeout_ms())
|
||||
return new_message
|
||||
else:
|
||||
try:
|
||||
new_message = await message.forward(super_chat_id)
|
||||
except exceptions.MessageCantBeForwarded:
|
||||
new_message = await message.copy_to(super_chat_id)
|
||||
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id,
|
||||
pexpire=ServerSettings.redis_timeout_ms())
|
||||
return new_message
|
||||
|
||||
|
||||
async def send_to_superchat(is_super_group: bool, message: types.Message, super_chat_id: int, bot):
|
||||
"""Пересылка сообщения от пользователя оператору (логика потоков сообщений)"""
|
||||
if is_super_group and bot.enable_threads:
|
||||
thread_first_message = await _redis.get(_thread_uniqie_id(bot.pk, message.chat.id))
|
||||
if thread_first_message:
|
||||
# переслать в супер-чат, отвечая на предыдущее сообщение
|
||||
try:
|
||||
new_message = await message.copy_to(super_chat_id, reply_to_message_id=int(thread_first_message))
|
||||
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id,
|
||||
pexpire=ServerSettings.redis_timeout_ms())
|
||||
except exceptions.BadRequest:
|
||||
new_message = await send_user_message(message, super_chat_id, bot)
|
||||
await _redis.set(_thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id,
|
||||
pexpire=ServerSettings.thread_timeout_ms())
|
||||
else:
|
||||
# переслать супер-чат
|
||||
new_message = await send_user_message(message, super_chat_id, bot)
|
||||
await _redis.set(_thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id,
|
||||
pexpire=ServerSettings.thread_timeout_ms())
|
||||
else: # личные сообщения не поддерживают потоки сообщений: просто отправляем сообщение
|
||||
await send_user_message(message, super_chat_id, bot)
|
||||
|
||||
|
||||
async def handle_user_message(message: types.Message, super_chat_id: int, bot):
|
||||
"""Обычный пользователь прислал сообщение в бот, нужно переслать его операторам"""
|
||||
_ = _get_translator(message)
|
||||
is_super_group = super_chat_id < 0
|
||||
|
||||
# Проверить, не забанен ли пользователь
|
||||
banned = await bot.banned_users.filter(telegram_id=message.chat.id)
|
||||
if banned:
|
||||
return SendMessage(chat_id=message.chat.id,
|
||||
text=_("Вы заблокированы в этом боте"))
|
||||
|
||||
# Проверить анти-флуд
|
||||
if bot.enable_antiflood:
|
||||
if await _redis.get(_antiflood_marker_uid(bot.pk, message.chat.id)):
|
||||
return SendMessage(chat_id=message.chat.id,
|
||||
text=_("Слишком много сообщений, подождите одну минуту"))
|
||||
await _redis.setex(_antiflood_marker_uid(bot.pk, message.chat.id), 60, 1)
|
||||
|
||||
# Пересылаем сообщение в супер-чат
|
||||
try:
|
||||
await send_to_superchat(is_super_group, message, super_chat_id, bot)
|
||||
except (exceptions.Unauthorized, exceptions.ChatNotFound):
|
||||
return SendMessage(chat_id=message.chat.id, text=_("Не удаётся связаться с владельцем бота"))
|
||||
except exceptions.TelegramAPIError as err:
|
||||
_logger.error(f"(exception on forwarding) {err}")
|
||||
return
|
||||
|
||||
bot.incoming_messages_count = F("incoming_messages_count") + 1
|
||||
await bot.save(update_fields=["incoming_messages_count"])
|
||||
|
||||
# И отправить пользователю специальный текст, если он указан и если давно не отправляли
|
||||
if bot.second_text:
|
||||
send_auto = not await _redis.get(_last_message_uid(bot.pk, message.chat.id))
|
||||
await _redis.setex(_last_message_uid(bot.pk, message.chat.id), 60 * 60 * 3, 1)
|
||||
if send_auto:
|
||||
return SendMessage(chat_id=message.chat.id, text=bot.second_text, parse_mode="HTML")
|
||||
|
||||
|
||||
async def handle_operator_message(message: types.Message, super_chat_id: int, bot):
|
||||
"""Оператор написал что-то, нужно переслать сообщение обратно пользователю, или забанить его и т.д."""
|
||||
_ = _get_translator(message)
|
||||
|
||||
if message.reply_to_message:
|
||||
|
||||
if message.reply_to_message.from_user.id != message.bot.id:
|
||||
return # нас интересуют только ответы на сообщения бота
|
||||
|
||||
# В супер-чате кто-то ответил на сообщение пользователя, нужно переслать тому пользователю
|
||||
chat_id = await _redis.get(_message_unique_id(bot.pk, message.reply_to_message.message_id))
|
||||
if not chat_id:
|
||||
chat_id = message.reply_to_message.forward_from_chat
|
||||
if not chat_id:
|
||||
return SendMessage(chat_id=message.chat.id,
|
||||
text=_("<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
|
||||
"старое?)</i>"),
|
||||
parse_mode="HTML")
|
||||
chat_id = int(chat_id)
|
||||
|
||||
if message.text == "/ban":
|
||||
user, create = await BannedUser.get_or_create(telegram_id=chat_id, bot=bot)
|
||||
await user.save()
|
||||
return SendMessage(chat_id=message.chat.id, text=_("Пользователь заблокирован"))
|
||||
|
||||
if message.text == "/unban":
|
||||
banned_user = await bot.banned_users.filter(telegram_id=chat_id).first()
|
||||
if not banned_user:
|
||||
return SendMessage(chat_id=message.chat.id, text=_("Пользователь не был забанен"))
|
||||
else:
|
||||
await banned_user.delete()
|
||||
return SendMessage(chat_id=message.chat.id, text=_("Пользователь разбанен"))
|
||||
|
||||
try:
|
||||
await message.copy_to(chat_id)
|
||||
except (exceptions.MessageError, exceptions.Unauthorized):
|
||||
await message.reply(_("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"),
|
||||
parse_mode="HTML")
|
||||
return
|
||||
|
||||
bot.outgoing_messages_count = F("outgoing_messages_count") + 1
|
||||
await bot.save(update_fields=["outgoing_messages_count"])
|
||||
|
||||
elif super_chat_id > 0:
|
||||
# в супер-чате кто-то пишет сообщение сам себе, только для личных сообщений
|
||||
await message.forward(super_chat_id)
|
||||
# И отправить пользователю специальный текст, если он указан
|
||||
if bot.second_text:
|
||||
return SendMessage(chat_id=message.chat.id, text=bot.second_text, parse_mode="HTML")
|
||||
|
||||
|
||||
async def message_handler(message: types.Message, *args, **kwargs):
|
||||
_ = _get_translator(message)
|
||||
bot = db_bot_instance.get()
|
||||
|
||||
if message.text and message.text.startswith("/start"):
|
||||
if message.text and message.text == "/start":
|
||||
# На команду start нужно ответить, не пересылая сообщение никуда
|
||||
return SendMessage(chat_id=message.chat.id,
|
||||
text=bot.start_text + ServerSettings.append_text())
|
||||
text = bot.start_text
|
||||
if bot.enable_olgram_text:
|
||||
text += _(ServerSettings.append_text())
|
||||
return SendMessage(chat_id=message.chat.id, text=text, parse_mode="HTML")
|
||||
|
||||
if message.text and message.text == "/security_policy":
|
||||
# На команду security_policy нужно ответить, не пересылая сообщение никуда
|
||||
return _on_security_policy(message, bot)
|
||||
|
||||
super_chat_id = await bot.super_chat_id()
|
||||
|
||||
if message.chat.id != super_chat_id:
|
||||
# Это обычный чат: сообщение нужно переслать в супер-чат
|
||||
new_message = await message.forward(super_chat_id)
|
||||
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id)
|
||||
# Это обычный чат
|
||||
return await handle_user_message(message, super_chat_id, bot)
|
||||
else:
|
||||
# Это супер-чат
|
||||
if message.reply_to_message:
|
||||
# Ответ из супер-чата переслать тому пользователю,
|
||||
chat_id = await _redis.get(_message_unique_id(bot.pk, message.reply_to_message.message_id))
|
||||
if not chat_id:
|
||||
chat_id = message.reply_to_message.forward_from_chat
|
||||
if not chat_id:
|
||||
return SendMessage(chat_id=message.chat.id,
|
||||
text="<i>Невозможно переслать сообщение: автор не найден</i>",
|
||||
parse_mode="HTML")
|
||||
chat_id = int(chat_id)
|
||||
try:
|
||||
await message.copy_to(chat_id)
|
||||
except (exceptions.MessageError, exceptions.BotBlocked):
|
||||
await message.reply("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>",
|
||||
parse_mode="HTML")
|
||||
return
|
||||
else:
|
||||
await message.forward(super_chat_id)
|
||||
return await handle_operator_message(message, super_chat_id, bot)
|
||||
|
||||
|
||||
async def edited_message_handler(message: types.Message, *args, **kwargs):
|
||||
return await message_handler(message, *args, **kwargs, is_edited=True)
|
||||
|
||||
|
||||
async def receive_invite(message: types.Message):
|
||||
@ -81,6 +255,18 @@ async def receive_invite(message: types.Message):
|
||||
break
|
||||
|
||||
|
||||
async def receive_group_create(message: types.Message):
|
||||
bot = db_bot_instance.get()
|
||||
|
||||
chat, _ = await GroupChat.get_or_create(chat_id=message.chat.id,
|
||||
defaults={"name": message.chat.full_name})
|
||||
chat.name = message.chat.full_name
|
||||
await chat.save()
|
||||
if chat not in await bot.group_chats.all():
|
||||
await bot.group_chats.add(chat)
|
||||
await bot.save()
|
||||
|
||||
|
||||
async def receive_left(message: types.Message):
|
||||
bot = db_bot_instance.get()
|
||||
if message.left_chat_member.id == message.bot.id:
|
||||
@ -93,6 +279,23 @@ async def receive_left(message: types.Message):
|
||||
await bot.save()
|
||||
|
||||
|
||||
async def receive_inline(inline_query):
|
||||
_logger.info("inline handler")
|
||||
bot = db_bot_instance.get()
|
||||
return await inline_handler(inline_query, bot)
|
||||
|
||||
|
||||
async def receive_migrate(message: types.Message):
|
||||
bot = db_bot_instance.get()
|
||||
from_id = message.chat.id
|
||||
to_id = message.migrate_to_chat_id
|
||||
|
||||
chats = await bot.group_chats.filter(chat_id=from_id)
|
||||
for chat in chats:
|
||||
chat.chat_id = to_id
|
||||
await chat.save(update_fields=["chat_id"])
|
||||
|
||||
|
||||
class CustomRequestHandler(WebhookRequestHandler):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -106,19 +309,26 @@ class CustomRequestHandler(WebhookRequestHandler):
|
||||
if not bot:
|
||||
return None
|
||||
db_bot_instance.set(bot)
|
||||
dp = Dispatcher(AioBot(bot.token))
|
||||
dp = Dispatcher(AioBot(bot.decrypted_token()))
|
||||
|
||||
supported_messages = [types.ContentType.TEXT,
|
||||
types.ContentType.CONTACT,
|
||||
types.ContentType.ANIMATION,
|
||||
types.ContentType.AUDIO,
|
||||
types.ContentType.DOCUMENT,
|
||||
types.ContentType.PHOTO,
|
||||
types.ContentType.STICKER,
|
||||
types.ContentType.VIDEO,
|
||||
types.ContentType.VOICE,
|
||||
types.ContentType.LOCATION]
|
||||
dp.register_message_handler(message_handler, content_types=supported_messages)
|
||||
dp.register_edited_message_handler(edited_message_handler, content_types=supported_messages)
|
||||
|
||||
dp.register_message_handler(message_handler, content_types=[types.ContentType.TEXT,
|
||||
types.ContentType.CONTACT,
|
||||
types.ContentType.ANIMATION,
|
||||
types.ContentType.AUDIO,
|
||||
types.ContentType.DOCUMENT,
|
||||
types.ContentType.PHOTO,
|
||||
types.ContentType.STICKER,
|
||||
types.ContentType.VIDEO,
|
||||
types.ContentType.VOICE])
|
||||
dp.register_message_handler(receive_invite, content_types=[types.ContentType.NEW_CHAT_MEMBERS])
|
||||
dp.register_message_handler(receive_left, content_types=[types.ContentType.LEFT_CHAT_MEMBER])
|
||||
dp.register_message_handler(receive_migrate, content_types=[types.ContentType.MIGRATE_TO_CHAT_ID])
|
||||
dp.register_message_handler(receive_group_create, content_types=[types.ContentType.GROUP_CHAT_CREATED])
|
||||
dp.register_inline_handler(receive_inline)
|
||||
|
||||
return dp
|
||||
|
||||
|
56
server/inlines.py
Normal file
@ -0,0 +1,56 @@
|
||||
from aiocache import cached
|
||||
import hashlib
|
||||
from aiogram.types import InlineQuery, InputTextMessageContent, InlineQueryResultArticle
|
||||
from aiogram.bot import Bot as AioBot
|
||||
|
||||
from olgram.models.models import Bot
|
||||
import typing as ty
|
||||
|
||||
|
||||
@cached(ttl=60)
|
||||
async def get_phrases(bot: Bot) -> ty.List:
|
||||
objects = await bot.answers
|
||||
return [obj.text for obj in objects]
|
||||
|
||||
|
||||
async def check_chat_member(chat_id: int, user_id: int, bot: AioBot) -> bool:
|
||||
member = await bot.get_chat_member(chat_id, user_id)
|
||||
return member.is_chat_member()
|
||||
|
||||
|
||||
@cached(ttl=60)
|
||||
async def check_permissions(inline_query: InlineQuery, bot: Bot):
|
||||
user_id = inline_query.from_user.id
|
||||
super_chat_id = await bot.super_chat_id()
|
||||
|
||||
if super_chat_id == user_id:
|
||||
return True
|
||||
|
||||
if super_chat_id < 0: # Group chat
|
||||
is_member = await check_chat_member(super_chat_id, user_id, inline_query.bot)
|
||||
return is_member
|
||||
|
||||
return False
|
||||
|
||||
|
||||
async def inline_handler(inline_query: InlineQuery, bot: Bot):
|
||||
# Check permissions at first
|
||||
allow = await check_permissions(inline_query, bot)
|
||||
if not allow:
|
||||
return await inline_query.answer([]) # forbidden
|
||||
|
||||
all_phrases = await get_phrases(bot)
|
||||
phrases = [phrase for phrase in all_phrases if inline_query.query.lower() in phrase.lower()]
|
||||
items = []
|
||||
for phrase in phrases:
|
||||
|
||||
input_content = InputTextMessageContent(phrase)
|
||||
result_id: str = hashlib.md5(phrase.encode()).hexdigest()
|
||||
item = InlineQueryResultArticle(
|
||||
id=result_id,
|
||||
title=phrase,
|
||||
input_message_content=input_content,
|
||||
)
|
||||
items.append(item)
|
||||
|
||||
await inline_query.answer(results=items)
|
@ -1,9 +1,11 @@
|
||||
from aiogram import Bot as AioBot
|
||||
from aiogram.types import BotCommand
|
||||
from olgram.models.models import Bot
|
||||
from aiohttp import web
|
||||
from asyncio import get_event_loop
|
||||
import ssl
|
||||
from olgram.settings import ServerSettings
|
||||
from locales.locale import _
|
||||
from .custom import CustomRequestHandler
|
||||
|
||||
import logging
|
||||
@ -26,14 +28,20 @@ async def register_token(bot: Bot) -> bool:
|
||||
:param bot: Бот
|
||||
:return: получилось ли
|
||||
"""
|
||||
await unregister_token(bot.token)
|
||||
await unregister_token(bot.decrypted_token())
|
||||
|
||||
a_bot = AioBot(bot.token)
|
||||
a_bot = AioBot(bot.decrypted_token())
|
||||
certificate = None
|
||||
if ServerSettings.use_custom_cert():
|
||||
certificate = open(ServerSettings.public_path(), 'rb')
|
||||
|
||||
res = await a_bot.set_webhook(url_for_bot(bot), certificate=certificate, drop_pending_updates=True)
|
||||
res = await a_bot.set_webhook(url_for_bot(bot), certificate=certificate, drop_pending_updates=True,
|
||||
max_connections=10)
|
||||
await a_bot.set_my_commands([
|
||||
BotCommand("/start", _("(Пере)запустить бота")),
|
||||
BotCommand("/security_policy", _("Политика конфиденциальности"))
|
||||
])
|
||||
|
||||
await a_bot.session.close()
|
||||
del a_bot
|
||||
return res
|
||||
@ -65,5 +73,5 @@ def main():
|
||||
runner = web.AppRunner(app)
|
||||
loop.run_until_complete(runner.setup())
|
||||
logger.info("Server initialization done")
|
||||
site = web.TCPSite(runner, host=ServerSettings.app_host(), port=ServerSettings.app_port(), ssl_context=context)
|
||||
site = web.TCPSite(runner, port=ServerSettings.app_port(), ssl_context=context)
|
||||
return site
|
||||
|