Compare commits
261 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2305590899 | ||
|
a34e633b98 | ||
|
16da3634db | ||
|
a0c6c9415e | ||
|
dab803a4e8 | ||
|
0ceea778fe | ||
|
7ce6df7fd9 | ||
|
59da56d463 | ||
|
1c12730a4e | ||
|
d0b570baa9 | ||
|
1b1fe239f8 | ||
|
214824db14 | ||
|
147fc2a665 | ||
|
c027ec656b | ||
|
f79a9f317d | ||
|
eeb6c65f36 | ||
|
7a0ce10c56 | ||
|
44f39e4de0 | ||
|
120fdef189 | ||
|
f26958518c | ||
|
acb62fb644 | ||
|
ae45374490 | ||
|
7aacb2e38f | ||
|
0881c86349 | ||
|
1dd4d4d7fd | ||
|
82b68d1d9f | ||
|
02e91a596c | ||
|
a5e6fbce34 | ||
|
28ed36ffeb | ||
|
601c16622d | ||
|
9e46041d0f | ||
|
f41e17a15c | ||
|
a262d4e488 | ||
|
bb1456dda1 | ||
|
756f0bd89a | ||
|
6acc2068de | ||
|
d478e9d8e9 | ||
|
52864ed729 | ||
|
ac09e42f94 | ||
|
afc5389520 | ||
|
3e1f89034a | ||
|
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
|
||||
|
|
25
.readthedocs.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Read the Docs configuration file for Sphinx projects
|
||||
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
|
||||
# Required
|
||||
|
||||
version: 2
|
||||
|
||||
|
||||
# Set the OS, Python version and other tools you might need
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.8"
|
||||
jobs:
|
||||
post_create_environment:
|
||||
- python -m pip install sphinx_rtd_theme
|
||||
|
||||
# Build documentation in the "docs/" directory with Sphinx
|
||||
|
||||
sphinx:
|
||||
|
||||
configuration: docs/source/conf.py
|
21
Dockerfile
|
@ -1,11 +1,26 @@
|
|||
FROM python:3.8-buster
|
||||
|
||||
COPY . /app
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
POETRY_VERSION=1.5.1 \
|
||||
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>`_.
|
52
docs/source/additional.rst
Normal file
|
@ -0,0 +1,52 @@
|
|||
Дополнительно
|
||||
=============
|
||||
|
||||
Донаты
|
||||
----------------
|
||||
|
||||
На аренду сервера для этого проекта
|
||||
|
||||
Bitcoin:
|
||||
``bc1qlq7cm5chc8flr3fy8ewk967aknq3dwmxtwn9hl``
|
||||
|
||||
Litecoin:
|
||||
``ltc1qxajsvz0lw44aa5nytuch8cp2g8x7a4cdase4y7``
|
||||
|
||||
Monero:
|
||||
``84ymMfpw3vxFxsgmYbFURMiZLgQCmhKsZNiZiqZRbpH2WRka2UDjyDVZpX8XH1cZ9d5EghvPXrF5hEuzvK5NvHGE8za4Gmk``
|
||||
|
||||
Как убрать "Этот бот создан с помощью ..."
|
||||
----------------
|
||||
Напишите нам на `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
|
||||
|
||||
|
||||
История изменений
|
||||
----------------
|
||||
|
||||
- `2024-03-15` Тэги
|
||||
- `2024-03-02` Рассылки
|
||||
- `2024-03-01` Непрерывные потоки сообщений (опция)
|
||||
- `2024-02-17` Опция смены режима работы автоответчика: автоответчик отвечает на КАЖДОЕ сообщение
|
||||
- `2024-01-12` Мультиязычность (стартовое сообщение и автоответчик)
|
||||
- `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 = '2024, 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>`_.
|
83
docs/source/options.rst
Normal file
|
@ -0,0 +1,83 @@
|
|||
Опции
|
||||
=============
|
||||
|
||||
.. _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:
|
||||
|
||||
Защита от флуда
|
||||
---------------
|
||||
|
||||
При включении этой опции пользователю запрещается отправлять больше одного сообщения в минуту. Используйте её, если
|
||||
не успеваете обрабатывать входящие сообщения.
|
||||
|
||||
|
||||
.. _always_second_message:
|
||||
|
||||
Использовать автоответчик всегда
|
||||
--------------------------------
|
||||
|
||||
По-умолчанию автоответчик отвечает только на первое сообщение в диалоге с пользователем. Чтобы автоответчик отвечал на
|
||||
КАЖДОЕ входящее сообщение, включите эту опцию.
|
||||
|
||||
|
||||
.. thread_interrupt:
|
||||
|
||||
Прерывать поток
|
||||
--------------------------------
|
||||
|
||||
По-умолчанию поток сообщений от одного пользователя прерывается каждые 24 часа. Без этой опции поток сообщений не
|
||||
прерывается никогда.
|
||||
|
||||
|
||||
.. _mailing:
|
||||
|
||||
Рассылка
|
||||
---------------
|
||||
|
||||
После включения этой опции ваш бот будет запоминать всех пользователей, которые пишут в ваш бот.
|
||||
Вы сможете запустить рассылку по этим пользователям.
|
||||
|
||||
.. note::
|
||||
|
||||
Включение этой опции меняет текст политики конфиденциальности вашего feedback бота (команда /security_policy)
|
||||
и может отпугнуть некоторых пользователей. Не включайте эту опцию без необходимости.
|
128
docs/source/quick_start.rst
Normal file
|
@ -0,0 +1,128 @@
|
|||
Быстрый старт
|
||||
=============
|
||||
|
||||
Как создать бота
|
||||
----------------
|
||||
|
||||
Перейдите по ссылке `@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
|
||||
|
||||
Теперь просто отправьте новый текст приветствия.
|
||||
|
||||
.. note::
|
||||
|
||||
Чтобы настроить особый текст приветствия для, например, русскоязычных пользователей (т.е. тех пользователей, у
|
||||
которых в настройках Telegram выставлена русская локализация), нажмите кнопку "Руссикй 🇷🇺" и только потом отправьте
|
||||
текст приветствия. Чтобы отредактировать текст приветствия для всех остальных языков, нажмите "[все языки]".
|
||||
|
||||
|
||||
Как привязать бота к групповому чату
|
||||
------------------------------------
|
||||
|
||||
По-умолчанию ваш бот пересылает сообщения от пользователей вам в личные сообщения. Бота можно привязать к групповому
|
||||
чату. Для этого добавьте его в групповой чат. Затем откройте список ботов, как в примере выше, выберите нужного бота
|
||||
и нажмите кнопку "Чат":
|
||||
|
||||
.. 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>`_.
|
||||
|
||||
Тэги пользователей
|
||||
------------------
|
||||
|
||||
Пользователям можно проставлять теги. Например, в ответ на сообщение пользователя написать:
|
||||
|
||||
```
|
||||
/tag #important
|
||||
```
|
||||
|
||||
Тогда в user info (см. раздел опции) помимо информации о пользователе будет тег #important
|
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
|
||||
|
|
813
locales/en/LC_MESSAGES/olgram.po
Normal file
|
@ -0,0 +1,813 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2024-03-02 19:47+0400\n"
|
||||
"PO-Revision-Date: 2024-03-02 19:48+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.4.2\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:27
|
||||
msgid "Бот удалён"
|
||||
msgstr "Bot removed"
|
||||
|
||||
#: olgram/commands/bot_actions.py:49 olgram/commands/bot_actions.py:67
|
||||
msgid "Текст сброшен"
|
||||
msgstr "Text is reset"
|
||||
|
||||
#: olgram/commands/bot_actions.py:81
|
||||
msgid "Выбран личный чат"
|
||||
msgstr "Personal chat selected"
|
||||
|
||||
#: olgram/commands/bot_actions.py:94
|
||||
msgid "Бот вышел из чатов"
|
||||
msgstr "Bot leaved chats"
|
||||
|
||||
#: olgram/commands/bot_actions.py:100
|
||||
msgid "Нельзя привязать бота к этому чату"
|
||||
msgstr "You can't bind a bot to this chat room"
|
||||
|
||||
#: olgram/commands/bot_actions.py:104
|
||||
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/info.py:40
|
||||
msgid "Рекламную плашку выключили: {0}\n"
|
||||
msgstr "Ad disabled:: {0}\n"
|
||||
|
||||
#: olgram/commands/menu.py:33
|
||||
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:48
|
||||
msgid "Ваши боты"
|
||||
msgstr "Your bots"
|
||||
|
||||
#: olgram/commands/menu.py:69
|
||||
msgid "Личные сообщения"
|
||||
msgstr "Personal messages"
|
||||
|
||||
#: olgram/commands/menu.py:74
|
||||
msgid "❗️ Выйти из всех чатов"
|
||||
msgstr "❗️ Leave all chats"
|
||||
|
||||
#: olgram/commands/menu.py:79 olgram/commands/menu.py:124
|
||||
#: olgram/commands/menu.py:156 olgram/commands/menu.py:209
|
||||
#: olgram/commands/menu.py:390
|
||||
msgid "<< Назад"
|
||||
msgstr "<< Back"
|
||||
|
||||
#: olgram/commands/menu.py:85
|
||||
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:92
|
||||
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:104
|
||||
msgid "Текст"
|
||||
msgstr "Text"
|
||||
|
||||
#: olgram/commands/menu.py:109
|
||||
msgid "Чат"
|
||||
msgstr "Chat"
|
||||
|
||||
#: olgram/commands/menu.py:114
|
||||
msgid "Удалить бот"
|
||||
msgstr "Delete bot"
|
||||
|
||||
#: olgram/commands/menu.py:119
|
||||
msgid "Статистика"
|
||||
msgstr "Statistics"
|
||||
|
||||
#: olgram/commands/menu.py:128
|
||||
msgid "Опции"
|
||||
msgstr "Options"
|
||||
|
||||
#: olgram/commands/menu.py:134 olgram/commands/menu.py:190
|
||||
msgid "Рассылка"
|
||||
msgstr "Mailing"
|
||||
|
||||
#: olgram/commands/menu.py:139
|
||||
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:151
|
||||
msgid "Да, удалить бот"
|
||||
msgstr "Yes, delete the bot"
|
||||
|
||||
#: olgram/commands/menu.py:160
|
||||
msgid ""
|
||||
"\n"
|
||||
" Вы уверены, что хотите удалить бота @{0}?\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Are you sure you want to delete the bot @{0}?\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:169
|
||||
msgid "Потоки сообщений"
|
||||
msgstr "Message threads"
|
||||
|
||||
#: olgram/commands/menu.py:174
|
||||
msgid "Данные пользователя"
|
||||
msgstr "User data"
|
||||
|
||||
#: olgram/commands/menu.py:179
|
||||
msgid "Антифлуд"
|
||||
msgstr "Antiflood"
|
||||
|
||||
#: olgram/commands/menu.py:184
|
||||
msgid "Автоответчик всегда"
|
||||
msgstr "Autorespond always"
|
||||
|
||||
#: olgram/commands/menu.py:195
|
||||
msgid "Прерывать поток"
|
||||
msgstr "Inteeupt thread"
|
||||
|
||||
#: olgram/commands/menu.py:203
|
||||
msgid "Olgram подпись"
|
||||
msgstr "Olgram signature"
|
||||
|
||||
#: olgram/commands/menu.py:214 olgram/commands/menu.py:215
|
||||
msgid "включены"
|
||||
msgstr "enabled"
|
||||
|
||||
#: olgram/commands/menu.py:214 olgram/commands/menu.py:215
|
||||
msgid "выключены"
|
||||
msgstr "disabled"
|
||||
|
||||
#: olgram/commands/menu.py:216
|
||||
#, fuzzy
|
||||
#| msgid "включены"
|
||||
msgid "включен"
|
||||
msgstr "enabled"
|
||||
|
||||
#: olgram/commands/menu.py:216 olgram/commands/menu.py:217
|
||||
#, fuzzy
|
||||
#| msgid "выключены"
|
||||
msgid "выключен"
|
||||
msgstr "disabled"
|
||||
|
||||
#: olgram/commands/menu.py:217
|
||||
#, fuzzy
|
||||
#| msgid "включены"
|
||||
msgid "включён"
|
||||
msgstr "enabled"
|
||||
|
||||
#: olgram/commands/menu.py:218
|
||||
msgid "да"
|
||||
msgstr "yes"
|
||||
|
||||
#: olgram/commands/menu.py:218
|
||||
msgid "нет"
|
||||
msgstr "no"
|
||||
|
||||
#: olgram/commands/menu.py:219 olgram/commands/menu.py:231
|
||||
msgid "включена"
|
||||
msgstr "enabled"
|
||||
|
||||
#: olgram/commands/menu.py:219 olgram/commands/menu.py:231
|
||||
msgid "выключена"
|
||||
msgstr "disabled"
|
||||
|
||||
#: olgram/commands/menu.py:220
|
||||
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"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#always_second_message\">Автоответчик всегда</a>: <b>{3}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#thread_interrupt\">Прерывать поток</a>: <b>{4}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#mailing\">Рассылка</a>: <b>{5}</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\">Anti-flood</a>: <b>{2}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#always_second_message\">Autorespond always</a>: <b>{3}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#thread_interrupt\">Interrupt threads</a>: <b>{4}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#mailing\">Mailing</a>: <b>{5}</b>\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:232
|
||||
msgid "Olgram подпись: <b>{0}</b>"
|
||||
msgstr "Olgram signature: <b>{0}</b>"
|
||||
|
||||
#: olgram/commands/menu.py:259 olgram/commands/menu.py:421
|
||||
#: olgram/commands/menu.py:480
|
||||
msgid "<< Завершить редактирование"
|
||||
msgstr "<< Finish editing"
|
||||
|
||||
#: olgram/commands/menu.py:263
|
||||
msgid "Автоответчик"
|
||||
msgstr "Autoresponder"
|
||||
|
||||
#: olgram/commands/menu.py:268 olgram/commands/menu.py:435
|
||||
msgid "Сбросить текст"
|
||||
msgstr "Reset text"
|
||||
|
||||
#: olgram/commands/menu.py:273 olgram/commands/menu.py:440
|
||||
msgid "[все языки]"
|
||||
msgstr "[all languages]"
|
||||
|
||||
#: olgram/commands/menu.py:290
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст, который отправляется после того, как "
|
||||
"пользователь отправит вашему боту @{0}\n"
|
||||
" команду /start\n"
|
||||
"\n"
|
||||
" Текущий текст{2}:\n"
|
||||
" <pre>{1}</pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:300 olgram/commands/menu.py:467
|
||||
msgid " (для языка {0})"
|
||||
msgstr " (for language {0})"
|
||||
|
||||
#: olgram/commands/menu.py:313
|
||||
msgid "<< Отменить рассылку"
|
||||
msgstr "<< Cancel"
|
||||
|
||||
#: olgram/commands/menu.py:317
|
||||
msgid ""
|
||||
"\n"
|
||||
" Напишите сообщение, которое нужно разослать всем подписчикам вашего бота "
|
||||
"@{0}. \n"
|
||||
" У сообщения будет до {1} получателей. \n"
|
||||
" Учтите, что\n"
|
||||
" 1. Рассылается только одно сообщение за раз (в т.ч. только одна "
|
||||
"картинка)\n"
|
||||
" 2. Когда рассылка запущена, её нельзя отменить \n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Please send mailing message to send all @{0} subscribers. \n"
|
||||
" Message will have up to {1} recipients. \n"
|
||||
" Take note:\n"
|
||||
" 1. Only one message per mailing\n"
|
||||
" 2.Mailing cant be interrupted \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:367
|
||||
msgid "Не удалось загрузить файл (слишком большой размер?)"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:374
|
||||
msgid "Да, начать рассылку"
|
||||
msgstr "Yes, start mailing"
|
||||
|
||||
#: olgram/commands/menu.py:394
|
||||
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:425
|
||||
msgid "Предыдущий текст"
|
||||
msgstr "Previous text"
|
||||
|
||||
#: olgram/commands/menu.py:430
|
||||
msgid "Шаблоны ответов..."
|
||||
msgstr "Answer templates..."
|
||||
|
||||
#: olgram/commands/menu.py:457
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
|
||||
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
|
||||
"отключено.\n"
|
||||
"\n"
|
||||
" Текущий текст{2}:\n"
|
||||
" <pre>{1}</pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:466
|
||||
msgid "отключено"
|
||||
msgstr "disabled"
|
||||
|
||||
#: olgram/commands/menu.py:484
|
||||
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:503
|
||||
msgid "(нет шаблонов)"
|
||||
msgstr "(no templates)"
|
||||
|
||||
#: olgram/commands/menu.py:565
|
||||
msgid "У вас нет шаблонов, чтобы их удалять"
|
||||
msgstr "You don't have templates to delete them"
|
||||
|
||||
#: olgram/commands/menu.py:567
|
||||
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
|
||||
msgstr "To delete a template, enter a number between 0 and {0}"
|
||||
|
||||
#: olgram/commands/menu.py:575
|
||||
msgid "У вашего бота уже слишком много шаблонов"
|
||||
msgstr "Your bot already has too many templates"
|
||||
|
||||
#: olgram/commands/menu.py:579
|
||||
msgid "Такой текст уже есть в списке шаблонов"
|
||||
msgstr "This text is already in the list of templates"
|
||||
|
||||
#: olgram/commands/menu.py:597
|
||||
msgid "У вас нет прав на этого бота"
|
||||
msgstr "You have no permissions to this bot"
|
||||
|
||||
#: olgram/commands/menu.py:617 olgram/commands/menu.py:643
|
||||
msgid "Рассылка была совсем недавно, подождите немного"
|
||||
msgstr "Mailing was recently, wait a bit please"
|
||||
|
||||
#: olgram/commands/menu.py:619 olgram/commands/menu.py:645
|
||||
msgid "Нет пользователей для рассылки"
|
||||
msgstr "No users for mailing"
|
||||
|
||||
#: olgram/commands/menu.py:647
|
||||
msgid "Рассылка запущена"
|
||||
msgstr "Mailing started"
|
||||
|
||||
#: olgram/commands/menu.py:649
|
||||
msgid "Рассылка завершена, отправлено {0} сообщений"
|
||||
msgstr "Mailing completed, {0} messages sent"
|
||||
|
||||
#: olgram/commands/menu.py:651
|
||||
msgid "Устарело, создайте новую рассылку"
|
||||
msgstr "Expired, please create new mailing"
|
||||
|
||||
#: 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:41
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу 😞"
|
||||
msgstr "The bot owner has restricted access to this functionality 😞"
|
||||
|
||||
#: olgram/utils/permissions.py:53
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу😞"
|
||||
msgstr "The owner of the bot has restricted access to this function😞"
|
||||
|
||||
#: server/custom.py:57
|
||||
msgid ""
|
||||
"<b>Политика конфиденциальности</b>\n"
|
||||
"\n"
|
||||
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
|
||||
"отправке сообщения (кроме команд /start и /security_policy) ваш "
|
||||
"идентификатор пользователя записывается в кеш на некоторое время и потом "
|
||||
"удаляется из кеша. Этот идентификатор используется для общения с "
|
||||
"оператором.\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 used for "
|
||||
"communicating with the operator\n"
|
||||
"\n"
|
||||
|
||||
#: server/custom.py:62
|
||||
msgid ""
|
||||
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
|
||||
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
|
||||
"силу настроек, которые оператор указал при создании бота.\n"
|
||||
"\n"
|
||||
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.\n"
|
||||
"\n"
|
||||
|
||||
#: server/custom.py:66
|
||||
msgid ""
|
||||
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
|
||||
"видеть ваш username, имя пользователя и другую информацию.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"Depending on your Telegram privacy settings, the operator may see your "
|
||||
"username, username and other information.\n"
|
||||
"\n"
|
||||
|
||||
#: server/custom.py:70
|
||||
msgid ""
|
||||
"В этом боте включена массовая рассылка в силу настроек, которые оператор "
|
||||
"указал при создании бота. Ваш идентификатор пользователя может быть записан "
|
||||
"в базу данных на долгое время"
|
||||
msgstr "Mailing enabled for this bot"
|
||||
|
||||
#: server/custom.py:73
|
||||
msgid "В этом боте нет массовой рассылки сообщений"
|
||||
msgstr "Mailing disabled for this bot"
|
||||
|
||||
#: server/custom.py:83
|
||||
msgid "Сообщение от пользователя "
|
||||
msgstr "Message from the user "
|
||||
|
||||
#: server/custom.py:157
|
||||
msgid "Вы заблокированы в этом боте"
|
||||
msgstr "You are blocked in this bot"
|
||||
|
||||
#: server/custom.py:163
|
||||
msgid "Слишком много сообщений, подождите одну минуту"
|
||||
msgstr "Too many messages, wait one minute"
|
||||
|
||||
#: server/custom.py:170
|
||||
msgid "Не удаётся связаться с владельцем бота"
|
||||
msgstr "Cannot contact the owner of the bot"
|
||||
|
||||
#: server/custom.py:202
|
||||
msgid ""
|
||||
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
|
||||
"старое?)</i>"
|
||||
msgstr ""
|
||||
"<i>Cannot forward this message: author not found (message too old?)</i>"
|
||||
|
||||
#: server/custom.py:210
|
||||
msgid "Пользователь заблокирован"
|
||||
msgstr "User is blocked"
|
||||
|
||||
#: server/custom.py:215
|
||||
msgid "Пользователь не был забанен"
|
||||
msgstr "The user was not banned"
|
||||
|
||||
#: server/custom.py:218
|
||||
msgid "Пользователь разбанен"
|
||||
msgstr "A user has been unlocked"
|
||||
|
||||
#: server/custom.py:223
|
||||
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,
|
||||
}
|
810
locales/uk/LC_MESSAGES/olgram.po
Normal file
|
@ -0,0 +1,810 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2024-03-02 19:47+0400\n"
|
||||
"PO-Revision-Date: 2024-03-02 19:48+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.4.2\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:27
|
||||
msgid "Бот удалён"
|
||||
msgstr "Бот видалений"
|
||||
|
||||
#: olgram/commands/bot_actions.py:49 olgram/commands/bot_actions.py:67
|
||||
msgid "Текст сброшен"
|
||||
msgstr "Текст скинутий"
|
||||
|
||||
#: olgram/commands/bot_actions.py:81
|
||||
msgid "Выбран личный чат"
|
||||
msgstr "Вибраний особистий чат"
|
||||
|
||||
#: olgram/commands/bot_actions.py:94
|
||||
msgid "Бот вышел из чатов"
|
||||
msgstr "Бот вийшов із чатів"
|
||||
|
||||
#: olgram/commands/bot_actions.py:100
|
||||
msgid "Нельзя привязать бота к этому чату"
|
||||
msgstr "Не можна прив'язати робота до цього чату"
|
||||
|
||||
#: olgram/commands/bot_actions.py:104
|
||||
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/info.py:40
|
||||
msgid "Рекламную плашку выключили: {0}\n"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:33
|
||||
msgid ""
|
||||
"\n"
|
||||
" У вас нет добавленных ботов.\n"
|
||||
"\n"
|
||||
" Отправьте команду /addbot, чтобы добавить бот.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" У вас немає доданих роботів.\n"
|
||||
"\n"
|
||||
" Надішліть команду /addbot, щоб додати бот.\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:48
|
||||
msgid "Ваши боты"
|
||||
msgstr "Ваші боти"
|
||||
|
||||
#: olgram/commands/menu.py:69
|
||||
msgid "Личные сообщения"
|
||||
msgstr "Особисті повідомлення"
|
||||
|
||||
#: olgram/commands/menu.py:74
|
||||
msgid "❗️ Выйти из всех чатов"
|
||||
msgstr "❗️ Вийти зі всіх чатів"
|
||||
|
||||
#: olgram/commands/menu.py:79 olgram/commands/menu.py:124
|
||||
#: olgram/commands/menu.py:156 olgram/commands/menu.py:209
|
||||
#: olgram/commands/menu.py:390
|
||||
msgid "<< Назад"
|
||||
msgstr "<< Назад"
|
||||
|
||||
#: olgram/commands/menu.py:85
|
||||
msgid ""
|
||||
"\n"
|
||||
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
|
||||
"вам в бот.\n"
|
||||
" Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню "
|
||||
"ещё раз и выберите добавленный чат.\n"
|
||||
" Если ваш бот состоял в групповом чате до того, как его добавили в "
|
||||
"Olgram - удалите бота из чата и добавьте\n"
|
||||
" снова.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Цей бот не доданий до чатів, тому всі повідомлення будуть приходити "
|
||||
"вам у бот.\n"
|
||||
" Щоб підключити чат — додайте бот @{0} до чату, відкрийте це меню ще "
|
||||
"раз і виберіть доданий чат.\n"
|
||||
" Якщо ваш бот перебував у груповому чаті до того, як його додали до "
|
||||
"Olgram - видаліть бота з чату та додайте\n"
|
||||
" знову.\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:92
|
||||
msgid ""
|
||||
"\n"
|
||||
" В этом разделе вы можете привязать бота @{0} к чату.\n"
|
||||
" Выберите чат, куда бот будет пересылать сообщения.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" У цьому розділі ви можете прив'язати робота @{0} до чату.\n"
|
||||
" Виберіть чат, куди бот пересилатиме повідомлення.\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:104
|
||||
msgid "Текст"
|
||||
msgstr "Текст"
|
||||
|
||||
#: olgram/commands/menu.py:109
|
||||
msgid "Чат"
|
||||
msgstr "Чат"
|
||||
|
||||
#: olgram/commands/menu.py:114
|
||||
msgid "Удалить бот"
|
||||
msgstr "Видалити бот"
|
||||
|
||||
#: olgram/commands/menu.py:119
|
||||
msgid "Статистика"
|
||||
msgstr "Статистика"
|
||||
|
||||
#: olgram/commands/menu.py:128
|
||||
msgid "Опции"
|
||||
msgstr "Опції"
|
||||
|
||||
#: olgram/commands/menu.py:134 olgram/commands/menu.py:190
|
||||
msgid "Рассылка"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:139
|
||||
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:151
|
||||
msgid "Да, удалить бот"
|
||||
msgstr "Так, видалити бот"
|
||||
|
||||
#: olgram/commands/menu.py:160
|
||||
msgid ""
|
||||
"\n"
|
||||
" Вы уверены, что хотите удалить бота @{0}?\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Ви впевнені, що хочете видалити бота @{0}?\n"
|
||||
" "
|
||||
|
||||
#: olgram/commands/menu.py:169
|
||||
msgid "Потоки сообщений"
|
||||
msgstr "Потоки повідомлень"
|
||||
|
||||
#: olgram/commands/menu.py:174
|
||||
msgid "Данные пользователя"
|
||||
msgstr "Дані користувача"
|
||||
|
||||
#: olgram/commands/menu.py:179
|
||||
msgid "Антифлуд"
|
||||
msgstr "Антифлуд"
|
||||
|
||||
#: olgram/commands/menu.py:184
|
||||
#, fuzzy
|
||||
#| msgid "Автоответчик"
|
||||
msgid "Автоответчик всегда"
|
||||
msgstr "Автовідповідач"
|
||||
|
||||
#: olgram/commands/menu.py:195
|
||||
msgid "Прерывать поток"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:203
|
||||
msgid "Olgram подпись"
|
||||
msgstr "Olgram підпис"
|
||||
|
||||
#: olgram/commands/menu.py:214 olgram/commands/menu.py:215
|
||||
msgid "включены"
|
||||
msgstr "включені"
|
||||
|
||||
#: olgram/commands/menu.py:214 olgram/commands/menu.py:215
|
||||
msgid "выключены"
|
||||
msgstr "вимкнені"
|
||||
|
||||
#: olgram/commands/menu.py:216
|
||||
#, fuzzy
|
||||
#| msgid "включены"
|
||||
msgid "включен"
|
||||
msgstr "включені"
|
||||
|
||||
#: olgram/commands/menu.py:216 olgram/commands/menu.py:217
|
||||
#, fuzzy
|
||||
#| msgid "выключены"
|
||||
msgid "выключен"
|
||||
msgstr "вимкнені"
|
||||
|
||||
#: olgram/commands/menu.py:217
|
||||
#, fuzzy
|
||||
#| msgid "включены"
|
||||
msgid "включён"
|
||||
msgstr "включені"
|
||||
|
||||
#: olgram/commands/menu.py:218
|
||||
msgid "да"
|
||||
msgstr "Ча"
|
||||
|
||||
#: olgram/commands/menu.py:218
|
||||
msgid "нет"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:219 olgram/commands/menu.py:231
|
||||
msgid "включена"
|
||||
msgstr "включена"
|
||||
|
||||
#: olgram/commands/menu.py:219 olgram/commands/menu.py:231
|
||||
msgid "выключена"
|
||||
msgstr "вимкнена"
|
||||
|
||||
#: olgram/commands/menu.py:220
|
||||
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"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#always_second_message\">Автоответчик всегда</a>: <b>{3}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#thread_interrupt\">Прерывать поток</a>: <b>{4}</b>\n"
|
||||
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
|
||||
"html#mailing\">Рассылка</a>: <b>{5}</b>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:232
|
||||
msgid "Olgram подпись: <b>{0}</b>"
|
||||
msgstr "Olgram підпис: <b>{0}</b>"
|
||||
|
||||
#: olgram/commands/menu.py:259 olgram/commands/menu.py:421
|
||||
#: olgram/commands/menu.py:480
|
||||
msgid "<< Завершить редактирование"
|
||||
msgstr "<< Завершити редагування"
|
||||
|
||||
#: olgram/commands/menu.py:263
|
||||
msgid "Автоответчик"
|
||||
msgstr "Автовідповідач"
|
||||
|
||||
#: olgram/commands/menu.py:268 olgram/commands/menu.py:435
|
||||
msgid "Сбросить текст"
|
||||
msgstr "Скинути текст"
|
||||
|
||||
#: olgram/commands/menu.py:273 olgram/commands/menu.py:440
|
||||
msgid "[все языки]"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:290
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст, который отправляется после того, как "
|
||||
"пользователь отправит вашему боту @{0}\n"
|
||||
" команду /start\n"
|
||||
"\n"
|
||||
" Текущий текст{2}:\n"
|
||||
" <pre>{1}</pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:300 olgram/commands/menu.py:467
|
||||
msgid " (для языка {0})"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:313
|
||||
#, fuzzy
|
||||
#| msgid "Отменить"
|
||||
msgid "<< Отменить рассылку"
|
||||
msgstr "Скасувати"
|
||||
|
||||
#: olgram/commands/menu.py:317
|
||||
msgid ""
|
||||
"\n"
|
||||
" Напишите сообщение, которое нужно разослать всем подписчикам вашего бота "
|
||||
"@{0}. \n"
|
||||
" У сообщения будет до {1} получателей. \n"
|
||||
" Учтите, что\n"
|
||||
" 1. Рассылается только одно сообщение за раз (в т.ч. только одна "
|
||||
"картинка)\n"
|
||||
" 2. Когда рассылка запущена, её нельзя отменить \n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:367
|
||||
msgid "Не удалось загрузить файл (слишком большой размер?)"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:374
|
||||
msgid "Да, начать рассылку"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:394
|
||||
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:425
|
||||
msgid "Предыдущий текст"
|
||||
msgstr "Попередній текст"
|
||||
|
||||
#: olgram/commands/menu.py:430
|
||||
msgid "Шаблоны ответов..."
|
||||
msgstr "Шаблони відповідей..."
|
||||
|
||||
#: olgram/commands/menu.py:457
|
||||
msgid ""
|
||||
"\n"
|
||||
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
|
||||
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
|
||||
"отключено.\n"
|
||||
"\n"
|
||||
" Текущий текст{2}:\n"
|
||||
" <pre>{1}</pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:466
|
||||
msgid "отключено"
|
||||
msgstr "відключено"
|
||||
|
||||
#: olgram/commands/menu.py:484
|
||||
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:503
|
||||
msgid "(нет шаблонов)"
|
||||
msgstr "(Немає шаблонів)"
|
||||
|
||||
#: olgram/commands/menu.py:565
|
||||
msgid "У вас нет шаблонов, чтобы их удалять"
|
||||
msgstr "У вас немає шаблонів, щоб їх видаляти"
|
||||
|
||||
#: olgram/commands/menu.py:567
|
||||
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
|
||||
msgstr "Неправильне число. Щоб видалити шаблон, введіть число від 0 до {0}"
|
||||
|
||||
#: olgram/commands/menu.py:575
|
||||
msgid "У вашего бота уже слишком много шаблонов"
|
||||
msgstr "У вашого бота вже дуже багато шаблонів"
|
||||
|
||||
#: olgram/commands/menu.py:579
|
||||
msgid "Такой текст уже есть в списке шаблонов"
|
||||
msgstr "Такий текст вже є у списку шаблонів"
|
||||
|
||||
#: olgram/commands/menu.py:597
|
||||
msgid "У вас нет прав на этого бота"
|
||||
msgstr "У вас немає прав на цього бота"
|
||||
|
||||
#: olgram/commands/menu.py:617 olgram/commands/menu.py:643
|
||||
msgid "Рассылка была совсем недавно, подождите немного"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:619 olgram/commands/menu.py:645
|
||||
msgid "Нет пользователей для рассылки"
|
||||
msgstr ""
|
||||
|
||||
#: olgram/commands/menu.py:647
|
||||
msgid "Рассылка запущена"
|
||||
msgstr "Розсилка запущена"
|
||||
|
||||
#: olgram/commands/menu.py:649
|
||||
msgid "Рассылка завершена, отправлено {0} сообщений"
|
||||
msgstr "Розсилка завершена, надіслано {0} повідомлень"
|
||||
|
||||
#: olgram/commands/menu.py:651
|
||||
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:41
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу 😞"
|
||||
msgstr "Власник бота обмежив доступ до цього функціоналу 😞"
|
||||
|
||||
#: olgram/utils/permissions.py:53
|
||||
msgid "Владелец бота ограничил доступ к этому функционалу😞"
|
||||
msgstr "Власник бота обмежив доступ до цього функціоналу 😞"
|
||||
|
||||
#: server/custom.py:57
|
||||
msgid ""
|
||||
"<b>Политика конфиденциальности</b>\n"
|
||||
"\n"
|
||||
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
|
||||
"отправке сообщения (кроме команд /start и /security_policy) ваш "
|
||||
"идентификатор пользователя записывается в кеш на некоторое время и потом "
|
||||
"удаляется из кеша. Этот идентификатор используется для общения с "
|
||||
"оператором.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"<b>Політика конфіденційності</b>\n"
|
||||
"\n"
|
||||
"Цей бот не зберігає ваші повідомлення, ім'я користувача та @username. При "
|
||||
"надсиланні повідомлення (крім команд /start та /security_policy) ваш "
|
||||
"ідентифікатор користувача записується в кеш на деякий час і потім "
|
||||
"видаляється з кеша. Цей ідентифікатор використовується для спілкування з "
|
||||
"оператором.\n"
|
||||
"\n"
|
||||
|
||||
#: server/custom.py:62
|
||||
msgid ""
|
||||
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
|
||||
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
|
||||
"силу настроек, которые оператор указал при создании бота.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"При надсиланні повідомлення (крім команд /start та /security_policy) "
|
||||
"оператор <b>бачить</b> ваше ім'я користувача, @username та ідентифікатор "
|
||||
"користувача через налаштування, які оператор вказав при створенні бота.\n"
|
||||
"\n"
|
||||
|
||||
#: server/custom.py:66
|
||||
msgid ""
|
||||
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
|
||||
"видеть ваш username, имя пользователя и другую информацию.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"Залежно від ваших налаштувань конфіденційності Telegram оператор може бачити "
|
||||
"ваш username, ім'я користувача та іншу інформацію.\n"
|
||||
"\n"
|
||||
|
||||
#: server/custom.py:70
|
||||
msgid ""
|
||||
"В этом боте включена массовая рассылка в силу настроек, которые оператор "
|
||||
"указал при создании бота. Ваш идентификатор пользователя может быть записан "
|
||||
"в базу данных на долгое время"
|
||||
msgstr ""
|
||||
"У цьому роботі включено масове розсилання в силу налаштувань, які оператор "
|
||||
"вказав при створенні робота. Ваш ідентифікатор користувача може бути "
|
||||
"записаний до бази даних на довгий час"
|
||||
|
||||
#: server/custom.py:73
|
||||
msgid "В этом боте нет массовой рассылки сообщений"
|
||||
msgstr "У цьому роботі немає масової розсилки повідомлень"
|
||||
|
||||
#: server/custom.py:83
|
||||
msgid "Сообщение от пользователя "
|
||||
msgstr "Допис від користувача "
|
||||
|
||||
#: server/custom.py:157
|
||||
msgid "Вы заблокированы в этом боте"
|
||||
msgstr "Ви заблоковані у цьому боті"
|
||||
|
||||
#: server/custom.py:163
|
||||
msgid "Слишком много сообщений, подождите одну минуту"
|
||||
msgstr "Забагато повідомлень, зачекайте одну хвилину"
|
||||
|
||||
#: server/custom.py:170
|
||||
msgid "Не удаётся связаться с владельцем бота"
|
||||
msgstr "Не вдається зв'язатися з власником бота"
|
||||
|
||||
#: server/custom.py:202
|
||||
msgid ""
|
||||
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
|
||||
"старое?)</i>"
|
||||
msgstr ""
|
||||
"<i>Неможливо надіслати повідомлення: автора не знайдено (повідомлення "
|
||||
"занадто старе?)</i>"
|
||||
|
||||
#: server/custom.py:210
|
||||
msgid "Пользователь заблокирован"
|
||||
msgstr "Користувач заблоковано"
|
||||
|
||||
#: server/custom.py:215
|
||||
msgid "Пользователь не был забанен"
|
||||
msgstr "Користувач не був забанений"
|
||||
|
||||
#: server/custom.py:218
|
||||
msgid "Пользователь разбанен"
|
||||
msgstr "Користувач розбанений"
|
||||
|
||||
#: server/custom.py:223
|
||||
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"
|
557
locales/zh/LC_MESSAGES/olgram.po
Normal file
|
@ -0,0 +1,557 @@
|
|||
# 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>{1}</pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 你现在正在编辑用户向你的机器人发送@{0}之后的文本。\n"
|
||||
" /start\n"
|
||||
"\n"
|
||||
" 目前的文本。\n"
|
||||
" <pre>{1}</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>{1}</pre>\n"
|
||||
" Отправьте сообщение, чтобы изменить текст.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 你现在正在编辑自动回复的文本。该信息会自动响应所有收到的@{0}信息而发送。"
|
||||
"默认情况下,它是禁用的。\n"
|
||||
"\n"
|
||||
" 目前的文本。\n"
|
||||
" <pre>{1}</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 "
|
37
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.menu # noqa: F401
|
||||
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,13 +44,20 @@ 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())
|
||||
|
||||
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())
|
|
@ -1,35 +1,70 @@
|
|||
"""
|
||||
Здесь работа с конкретным ботом
|
||||
"""
|
||||
import logging
|
||||
|
||||
from aiogram import types
|
||||
from aiogram.utils.exceptions import TelegramAPIError
|
||||
from olgram.models.models import Bot
|
||||
from asyncio import sleep
|
||||
from datetime import datetime
|
||||
from olgram.utils.mix import send_stored_message
|
||||
from aiogram.utils import exceptions
|
||||
from aiogram import Bot as AioBot
|
||||
from olgram.models.models import Bot, BotStartMessage, BotSecondMessage
|
||||
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 exceptions.Unauthorized:
|
||||
# Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы
|
||||
pass
|
||||
await bot.delete()
|
||||
await call.answer("Бот удалён")
|
||||
await call.answer(_("Бот удалён"))
|
||||
try:
|
||||
await call.message.delete()
|
||||
except TelegramAPIError:
|
||||
except exceptions.TelegramAPIError:
|
||||
pass
|
||||
|
||||
|
||||
async def reset_bot_text(bot: Bot, call: types.CallbackQuery):
|
||||
async def reset_bot_text(bot: Bot, call: types.CallbackQuery, state):
|
||||
"""
|
||||
Пользователь решил сбросить текст бота к default
|
||||
:param bot:
|
||||
:param call:
|
||||
:return:
|
||||
"""
|
||||
async with state.proxy() as proxy:
|
||||
lang = proxy.get("lang", "none")
|
||||
if lang == "none":
|
||||
await BotStartMessage.filter(bot=bot).delete()
|
||||
bot.start_text = bot._meta.fields_map['start_text'].default
|
||||
await bot.save()
|
||||
await call.answer("Текст сброшен")
|
||||
await bot.save(update_fields=["start_text"])
|
||||
else:
|
||||
await BotStartMessage.filter(bot=bot, locale=lang).delete()
|
||||
await call.answer(_("Текст сброшен"))
|
||||
|
||||
|
||||
async def reset_bot_second_text(bot: Bot, call: types.CallbackQuery, state):
|
||||
"""
|
||||
Пользователь решил сбросить second text бота
|
||||
:param bot:
|
||||
:param call:
|
||||
:return:
|
||||
"""
|
||||
async with state.proxy() as proxy:
|
||||
lang = proxy.get("lang", "none")
|
||||
if lang == "none":
|
||||
await BotSecondMessage.filter(bot=bot).delete()
|
||||
bot.second_text = bot._meta.fields_map['second_text'].default
|
||||
await bot.save(update_fields=["second_text"])
|
||||
else:
|
||||
await BotSecondMessage.filter(bot=bot, locale=lang).delete()
|
||||
await call.answer(_("Текст сброшен"))
|
||||
|
||||
|
||||
async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str):
|
||||
|
@ -43,13 +78,98 @@ 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 exceptions.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 always_second_message(bot: Bot, call: types.CallbackQuery):
|
||||
bot.enable_always_second_message = not bot.enable_always_second_message
|
||||
await bot.save(update_fields=["enable_always_second_message"])
|
||||
|
||||
|
||||
async def thread_interrupt(bot: Bot, call: types.CallbackQuery):
|
||||
bot.enable_thread_interrupt = not bot.enable_thread_interrupt
|
||||
await bot.save(update_fields=["enable_thread_interrupt"])
|
||||
|
||||
|
||||
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"])
|
||||
|
||||
|
||||
async def mailing(bot: Bot, call: types.CallbackQuery):
|
||||
bot.enable_mailing = not bot.enable_mailing
|
||||
await bot.save(update_fields=["enable_mailing"])
|
||||
|
||||
|
||||
async def tags(bot: Bot, call: types.CallbackQuery):
|
||||
bot.enable_tags = not bot.enable_tags
|
||||
await bot.save(update_fields=["enable_tags"])
|
||||
|
||||
|
||||
async def go_mailing(bot: Bot, context: dict) -> int:
|
||||
users = await bot.mailing_users
|
||||
a_bot = AioBot(bot.decrypted_token())
|
||||
|
||||
count = 0
|
||||
|
||||
for user in users:
|
||||
bot.last_mailing_at = datetime.now()
|
||||
await bot.save(update_fields=["last_mailing_at"])
|
||||
try:
|
||||
await sleep(0.05)
|
||||
try:
|
||||
file_id = await send_stored_message(context, a_bot, user.telegram_id)
|
||||
except exceptions.RetryAfter as err:
|
||||
await sleep(err.timeout)
|
||||
file_id = await send_stored_message(context, a_bot, user.telegram_id)
|
||||
|
||||
if file_id:
|
||||
context["mailing_id"] = file_id
|
||||
count += 1
|
||||
except (exceptions.ChatNotFound, exceptions.BotBlocked, exceptions.UserDeactivated):
|
||||
await user.delete()
|
||||
except Exception as err:
|
||||
logging.error("mailing error")
|
||||
logging.error(err, exc_info=True)
|
||||
pass
|
||||
|
||||
return count
|
||||
|
|
|
@ -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,15 @@
|
|||
import logging
|
||||
from io import BytesIO
|
||||
from olgram.router import dp
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from aiogram import types, Bot as AioBot
|
||||
from olgram.models.models import Bot, User
|
||||
from olgram.models.models import Bot, User, DefaultAnswer, BotStartMessage, BotSecondMessage
|
||||
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, send_stored_message
|
||||
from olgram.commands import bot_actions
|
||||
from locales.locale import _
|
||||
|
||||
import typing as ty
|
||||
|
||||
|
@ -27,11 +30,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 +45,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 +66,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="<< Назад",
|
||||
types.InlineKeyboardButton(text=_("❗️ Выйти из всех чатов"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
|
||||
chat="leave"))
|
||||
)
|
||||
keyboard.insert(
|
||||
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,76 +101,421 @@ 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))
|
||||
)
|
||||
if bot.enable_mailing:
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Рассылка"),
|
||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="go_mailing",
|
||||
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_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, chat_id: ty.Optional[int] = None):
|
||||
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))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Автоответчик всегда"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
||||
operation="always_second_message",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Рассылка"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="mailing",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Прерывать поток"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
||||
operation="thread_interrupt",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("Теги"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
||||
operation="tags",
|
||||
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 _("выключен")
|
||||
enable_always_second_message = _("включён") if bot.enable_always_second_message else _("выключен")
|
||||
thread_interrupt = _("да") if bot.enable_thread_interrupt else _("нет")
|
||||
mailing_turn = _("включена") if bot.enable_mailing else _("выключена")
|
||||
tags_turn = _("включены") if bot.enable_tags 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>
|
||||
<a href="https://olgram.readthedocs.io/ru/latest/options.html#always_second_message">Автоответчик всегда</a>: <b>{3}</b>
|
||||
<a href="https://olgram.readthedocs.io/ru/latest/options.html#thread_interrupt">Прерывать поток</a>: <b>{4}</b>
|
||||
<a href="https://olgram.readthedocs.io/ru/latest/options.html#mailing">Рассылка</a>: <b>{5}</b>
|
||||
Теги: <b>{6}</b>
|
||||
""")).format(thread_turn, info_turn, antiflood_turn, enable_always_second_message, thread_interrupt,
|
||||
mailing_turn, tags_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")
|
||||
|
||||
|
||||
languages = {
|
||||
"en": "English 🇺🇸",
|
||||
"ru": "Русский 🇷🇺",
|
||||
"uk": "Український 🇺🇦",
|
||||
"tr": "Türkçe 🇹🇷",
|
||||
"hy": "հայերեն 🇦🇲",
|
||||
"ka": "ქართული ენა 🇬🇪"
|
||||
}
|
||||
|
||||
|
||||
async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, chat_id: ty.Optional[int] = None,
|
||||
state=None):
|
||||
if call:
|
||||
await call.answer()
|
||||
|
||||
async with state.proxy() as proxy:
|
||||
lang = proxy.get("lang", "none")
|
||||
|
||||
prepared_languages = {ln.locale: ln.text for ln in await bot.start_texts}
|
||||
|
||||
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=3, bot_id=bot.id, operation="next_text",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.row(
|
||||
types.InlineKeyboardButton(text=_("Сбросить текст"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="reset_text",
|
||||
chat=empty))
|
||||
)
|
||||
keyboard.add(
|
||||
types.InlineKeyboardButton(text=("🟢 " if lang == "none" else "") + _("[все языки]"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
||||
operation="slang_none", chat=empty))
|
||||
)
|
||||
for code, name in languages.items():
|
||||
prefix = ""
|
||||
if code == lang:
|
||||
prefix = "🟢 "
|
||||
elif code in prepared_languages:
|
||||
prefix = "✔️ "
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=prefix + name,
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
||||
operation=f"slang_{code}",
|
||||
chat=empty))
|
||||
)
|
||||
|
||||
text = dedent(_("""
|
||||
Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту @{0}
|
||||
команду /start
|
||||
|
||||
Текущий текст{2}:
|
||||
<pre>{1}</pre>
|
||||
Отправьте сообщение, чтобы изменить текст.
|
||||
"""))
|
||||
text = text.format(bot.name,
|
||||
prepared_languages.get(lang, bot.start_text),
|
||||
_(" (для языка {0})").format(languages[lang]) if lang != "none" 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_mailing_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=1)
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=_("<< Отменить рассылку"),
|
||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
||||
)
|
||||
|
||||
text = dedent(_("""
|
||||
Напишите сообщение, которое нужно разослать всем подписчикам вашего бота @{0}.
|
||||
У сообщения будет до {1} получателей.
|
||||
Учтите, что
|
||||
1. Рассылается только одно сообщение за раз (в т.ч. только одна картинка)
|
||||
2. Когда рассылка запущена, её нельзя отменить
|
||||
"""))
|
||||
text = text.format(bot.name, len(await bot.mailing_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")
|
||||
|
||||
|
||||
@dp.message_handler(state="wait_mailing_text",
|
||||
content_types=[types.ContentType.TEXT,
|
||||
types.ContentType.LOCATION,
|
||||
types.ContentType.DOCUMENT,
|
||||
types.ContentType.PHOTO,
|
||||
types.ContentType.AUDIO,
|
||||
types.ContentType.VIDEO]) # TODO: not command
|
||||
async def mailing_text_received(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as proxy:
|
||||
bot_id = proxy["bot_id"]
|
||||
proxy["mailing_content_type"] = message.content_type
|
||||
|
||||
buffer = BytesIO()
|
||||
|
||||
if message.content_type == types.ContentType.TEXT:
|
||||
proxy["mailing_text"] = message.html_text
|
||||
elif message.content_type == types.ContentType.LOCATION:
|
||||
proxy["mailing_location"] = message.location
|
||||
elif message.content_type in (types.ContentType.PHOTO, types.ContentType.DOCUMENT, types.ContentType.AUDIO,
|
||||
types.ContentType.VIDEO):
|
||||
proxy["mailing_caption"] = message.caption
|
||||
|
||||
if message.content_type == types.ContentType.PHOTO:
|
||||
obj = message.photo[-1]
|
||||
elif message.content_type == types.ContentType.DOCUMENT:
|
||||
obj = message.document
|
||||
elif message.content_type == types.ContentType.AUDIO:
|
||||
obj = message.audio
|
||||
elif message.content_type == types.ContentType.VIDEO:
|
||||
obj = message.video
|
||||
if obj.file_size and obj.file_size > 4194304:
|
||||
return await message.answer(_("Слишком большой файл (4 Мб максимум)"))
|
||||
|
||||
try:
|
||||
await obj.download(buffer, timeout=5)
|
||||
except Exception as err:
|
||||
logging.error("Error downloading file")
|
||||
logging.error(err, exc_info=True)
|
||||
return await message.answer(_("Не удалось загрузить файл (слишком большой размер?)"))
|
||||
proxy["mailing_data"] = buffer.getvalue()
|
||||
proxy["mailing_file_name"] = getattr(obj, "file_name", None)
|
||||
_message_id = await send_stored_message(proxy, AioBot.get_current(), message.chat.id)
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=1)
|
||||
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=3, bot_id=bot_id, operation="go_go_mailing",
|
||||
chat=empty))
|
||||
)
|
||||
|
||||
await AioBot.get_current().send_message(message.chat.id, reply_to_message_id=_message_id,
|
||||
text="Вы уверены, что хотите разослать это сообщение всем пользователям?",
|
||||
reply_markup=keyboard)
|
||||
|
||||
|
||||
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=3, bot_id=bot.id, operation="reset_text",
|
||||
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))
|
||||
)
|
||||
|
||||
text = dedent("""
|
||||
Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту {0}
|
||||
команду /start
|
||||
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, state=None):
|
||||
if call:
|
||||
await call.answer()
|
||||
|
||||
async with state.proxy() as proxy:
|
||||
lang = proxy.get("lang", "none")
|
||||
|
||||
prepared_languages = {ln.locale: ln.text for ln in await bot.second_texts}
|
||||
|
||||
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))
|
||||
)
|
||||
keyboard.add(
|
||||
types.InlineKeyboardButton(text=("🟢 " if lang == "none" else "") + _("[все языки]"),
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
||||
operation="alang_none", chat=empty))
|
||||
)
|
||||
for code, name in languages.items():
|
||||
prefix = ""
|
||||
if code == lang:
|
||||
prefix = "🟢 "
|
||||
elif code in prepared_languages:
|
||||
prefix = "✔️ "
|
||||
keyboard.insert(
|
||||
types.InlineKeyboardButton(text=prefix + name,
|
||||
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
||||
operation=f"alang_{code}",
|
||||
chat=empty))
|
||||
)
|
||||
|
||||
text = dedent(_("""
|
||||
Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в ответ на все входящие сообщения @{0} \
|
||||
автоматически. По умолчанию оно отключено.
|
||||
|
||||
Текущий текст{2}:
|
||||
<pre>{1}</pre>
|
||||
Отправьте сообщение, чтобы изменить текст.
|
||||
"""))
|
||||
text = text.format(bot.name,
|
||||
prepared_languages.get(lang, bot.second_text or _("отключено")),
|
||||
_(" (для языка {0})").format(languages[lang]) if lang != "none" 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>
|
||||
Отправьте сообщение, чтобы изменить текст.
|
||||
""")
|
||||
text = text.format(bot.name, bot.start_text)
|
||||
Отправьте какую-нибудь фразу (например: "Ваш заказ готов, ожидайте!"), чтобы добавить её в шаблон.
|
||||
Чтобы удалить шаблон из списка, отправьте его номер в списке (например, 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:
|
||||
|
@ -171,10 +526,76 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] =
|
|||
async def start_text_received(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as proxy:
|
||||
bot_id = proxy.get("bot_id")
|
||||
lang = proxy.get("lang", "none")
|
||||
|
||||
bot = await Bot.get_or_none(pk=bot_id)
|
||||
bot.start_text = message.text
|
||||
await bot.save()
|
||||
await send_bot_text_menu(bot, chat_id=message.chat.id)
|
||||
if lang == "none":
|
||||
bot.start_text = message.html_text
|
||||
await bot.save(update_fields=["start_text"])
|
||||
else:
|
||||
obj, created = await BotStartMessage.get_or_create(bot=bot,
|
||||
locale=lang,
|
||||
defaults={"text": message.html_text})
|
||||
if not created:
|
||||
obj.text = message.html_text
|
||||
await obj.save(update_fields=["text"])
|
||||
await send_bot_text_menu(bot, chat_id=message.chat.id, state=state)
|
||||
|
||||
|
||||
@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")
|
||||
lang = proxy.get("lang", "none")
|
||||
|
||||
bot = await Bot.get_or_none(pk=bot_id)
|
||||
if lang == "none":
|
||||
bot.second_text = message.html_text
|
||||
await bot.save(update_fields=["second_text"])
|
||||
else:
|
||||
obj, created = await BotSecondMessage.get_or_create(bot=bot,
|
||||
locale=lang,
|
||||
defaults={"text": message.html_text})
|
||||
if not created:
|
||||
obj.text = message.html_text
|
||||
await obj.save(update_fields=["text"])
|
||||
if not bot.second_text:
|
||||
bot.second_text = message.html_text
|
||||
await bot.save(update_fields=["second_text"])
|
||||
await send_bot_second_text_menu(bot, chat_id=message.chat.id, state=state)
|
||||
|
||||
|
||||
@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="*")
|
||||
|
@ -187,10 +608,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,17 +622,98 @@ 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 == "go_mailing":
|
||||
if bot.last_mailing_at and bot.last_mailing_at >= datetime.now(tz=timezone.utc) - timedelta(minutes=5):
|
||||
return await call.answer(_("Рассылка была совсем недавно, подождите немного"), show_alert=True)
|
||||
if not await bot.mailing_users:
|
||||
return await call.answer(_("Нет пользователей для рассылки"))
|
||||
await state.set_state("wait_mailing_text")
|
||||
async with state.proxy() as proxy:
|
||||
proxy["bot_id"] = bot.id
|
||||
return await send_bot_mailing_menu(bot, call)
|
||||
if operation == "text":
|
||||
await state.set_state("wait_start_text")
|
||||
async with state.proxy() as proxy:
|
||||
proxy["bot_id"] = bot.id
|
||||
return await send_bot_text_menu(bot, call)
|
||||
return await send_bot_text_menu(bot, call, state=state)
|
||||
|
||||
if level == "3":
|
||||
if operation == "delete_yes":
|
||||
return await bot_actions.delete_bot(bot, call)
|
||||
if operation == "mailing":
|
||||
await bot_actions.mailing(bot, call)
|
||||
return await send_bot_settings_menu(bot, call)
|
||||
if operation == "go_go_mailing":
|
||||
if (await state.get_state()) == "wait_mailing_text":
|
||||
async with state.proxy() as proxy:
|
||||
mailing_data = dict(proxy)
|
||||
await state.reset_state()
|
||||
|
||||
if bot.last_mailing_at and bot.last_mailing_at >= datetime.now(tz=timezone.utc) - timedelta(minutes=5):
|
||||
return await call.answer(_("Рассылка была совсем недавно, подождите немного"), show_alert=True)
|
||||
if not await bot.mailing_users:
|
||||
return await call.answer(_("Нет пользователей для рассылки"))
|
||||
|
||||
await call.answer(_("Рассылка запущена"))
|
||||
count = await bot_actions.go_mailing(bot, mailing_data)
|
||||
return await call.message.answer(_("Рассылка завершена, отправлено {0} сообщений").format(count))
|
||||
else:
|
||||
return await call.answer(_("Устарело, создайте новую рассылку"))
|
||||
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 == "always_second_message":
|
||||
await bot_actions.always_second_message(bot, call)
|
||||
return await send_bot_settings_menu(bot, call)
|
||||
if operation == "mailing":
|
||||
await bot_actions.mailing(bot, call)
|
||||
return await send_bot_settings_menu(bot, call)
|
||||
if operation == "tags":
|
||||
await bot_actions.tags(bot, call)
|
||||
return await send_bot_settings_menu(bot, call)
|
||||
if operation == "thread_interrupt":
|
||||
await bot_actions.thread_interrupt(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)
|
||||
await bot_actions.reset_bot_text(bot, call, state)
|
||||
return await send_bot_text_menu(bot, call, state=state)
|
||||
if operation.startswith("slang_"):
|
||||
async with state.proxy() as proxy:
|
||||
lang = operation.replace("slang_", "")
|
||||
if lang == "none" or lang in languages:
|
||||
proxy["lang"] = lang
|
||||
return await send_bot_text_menu(bot, call, state=state)
|
||||
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, state=state)
|
||||
if operation.startswith("alang_"):
|
||||
async with state.proxy() as proxy:
|
||||
lang = operation.replace("alang_", "")
|
||||
if lang == "none" or lang in languages:
|
||||
proxy["lang"] = lang
|
||||
return await send_bot_second_text_menu(bot, call, state=state)
|
||||
if operation == "reset_second_text":
|
||||
await bot_actions.reset_bot_second_text(bot, call, state)
|
||||
return await send_bot_second_text_menu(bot, call, state=state)
|
||||
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";
|
9
olgram/migrations/models/15_20240112035625_update.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
-- upgrade --
|
||||
CREATE TABLE IF NOT EXISTS "bot_start_message" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"locale" VARCHAR(5) NOT NULL,
|
||||
"bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE,
|
||||
CONSTRAINT "uid_bot_start_m_bot_id_871cd1" UNIQUE ("bot_id", "locale")
|
||||
);
|
||||
-- downgrade --
|
||||
DROP TABLE IF EXISTS "bot_start_message";
|
4
olgram/migrations/models/16_20240112040146_update.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
-- upgrade --
|
||||
ALTER TABLE "bot_start_message" ADD "text" TEXT NOT NULL;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot_start_message" DROP COLUMN "text";
|
10
olgram/migrations/models/17_20240112045126_update.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
-- upgrade --
|
||||
CREATE TABLE IF NOT EXISTS "bot_second_message" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"locale" VARCHAR(5) NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE,
|
||||
CONSTRAINT "uid_bot_second__bot_id_432892" UNIQUE ("bot_id", "locale")
|
||||
);
|
||||
-- downgrade --
|
||||
DROP TABLE IF EXISTS "bot_second_message";
|
4
olgram/migrations/models/18_20240217035146_update.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "enable_always_second_message" BOOL NOT NULL DEFAULT False;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "enable_always_second_message";
|
4
olgram/migrations/models/19_20240301193152_update.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "enable_thread_interrupt" BOOL NOT NULL DEFAULT True;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "enable_thread_interrupt";
|
6
olgram/migrations/models/20_20240301215418_update.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "enable_mailing" BOOL NOT NULL DEFAULT False;
|
||||
ALTER TABLE "bot" ADD "last_mailing_at" TIMESTAMPTZ;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "enable_mailing";
|
||||
ALTER TABLE "bot" DROP COLUMN "last_mailing_at";
|
10
olgram/migrations/models/21_20240301222228_update.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
-- upgrade --
|
||||
CREATE TABLE IF NOT EXISTS "mailinguser" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"telegram_id" BIGINT NOT NULL,
|
||||
"bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE,
|
||||
CONSTRAINT "uid_mailinguser_bot_id_906a76" UNIQUE ("bot_id", "telegram_id")
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_mailinguser_telegra_55de60" ON "mailinguser" ("telegram_id");
|
||||
-- downgrade --
|
||||
DROP TABLE IF EXISTS "mailinguser";
|
4
olgram/migrations/models/22_20240317143246_update.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
-- upgrade --
|
||||
ALTER TABLE "bot_start_message" ALTER COLUMN "locale" TYPE VARCHAR(15) USING "locale"::VARCHAR(15);
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot_start_message" ALTER COLUMN "locale" TYPE VARCHAR(5) USING "locale"::VARCHAR(5);
|
4
olgram/migrations/models/24_20240322034823_update.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
-- upgrade --
|
||||
ALTER TABLE "bot" ADD "enable_tags" BOOL NOT NULL DEFAULT False;
|
||||
-- downgrade --
|
||||
ALTER TABLE "bot" DROP COLUMN "enable_tags";
|
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,24 +39,87 @@ 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)
|
||||
enable_always_second_message = fields.BooleanField(default=False)
|
||||
enable_thread_interrupt = fields.BooleanField(default=True)
|
||||
enable_mailing = fields.BooleanField(default=False)
|
||||
enable_tags = fields.BooleanField(default=False)
|
||||
last_mailing_at = fields.DatetimeField(null=True, default=None)
|
||||
|
||||
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'
|
||||
|
||||
|
||||
class BotStartMessage(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
bot = fields.ForeignKeyField("models.Bot", related_name="start_texts", on_delete=fields.CASCADE)
|
||||
locale = fields.CharField(max_length=15)
|
||||
text = fields.TextField()
|
||||
|
||||
class Meta:
|
||||
unique_together = ("bot", "locale")
|
||||
table = 'bot_start_message'
|
||||
|
||||
|
||||
class BotSecondMessage(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
bot = fields.ForeignKeyField("models.Bot", related_name="second_texts", on_delete=fields.CASCADE)
|
||||
locale = fields.CharField(max_length=5)
|
||||
text = fields.TextField()
|
||||
|
||||
class Meta:
|
||||
unique_together = ("bot", "locale")
|
||||
table = 'bot_second_message'
|
||||
|
||||
|
||||
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'
|
||||
|
||||
|
||||
class MailingUser(Model):
|
||||
id = fields.BigIntField(pk=True)
|
||||
telegram_id = fields.BigIntField(index=True)
|
||||
|
||||
bot = fields.ForeignKeyField("models.Bot", related_name="mailing_users", on_delete=fields.relational.CASCADE)
|
||||
|
||||
class Meta:
|
||||
table = 'mailinguser'
|
||||
unique_together = (("bot", "telegram_id"), )
|
||||
|
||||
|
||||
class GroupChat(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
chat_id = fields.BigIntField(index=True, unique=True)
|
||||
|
@ -46,3 +127,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,24 @@
|
|||
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 +28,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.7.3"
|
||||
|
||||
@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 +64,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 +93,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 +118,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 +144,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()}'
|
||||
|
@ -109,4 +160,6 @@ TORTOISE_ORM = {
|
|||
"default_connection": "default",
|
||||
},
|
||||
},
|
||||
"use_tz": False,
|
||||
"timezone": "UTC"
|
||||
}
|
||||
|
|
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()
|
|
@ -1,5 +1,8 @@
|
|||
import logging
|
||||
from io import BytesIO
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup
|
||||
from aiogram.utils.exceptions import TelegramAPIError
|
||||
from aiogram import types, Bot as AioBot
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
@ -22,8 +25,39 @@ 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)
|
||||
|
||||
|
||||
async def send_stored_message(storage: dict, bot: AioBot, chat_id: int):
|
||||
content_type = storage["mailing_content_type"]
|
||||
if content_type == types.ContentType.TEXT:
|
||||
return await bot.send_message(chat_id, storage["mailing_text"], parse_mode="HTML")
|
||||
if content_type == types.ContentType.LOCATION:
|
||||
return await bot.send_location(chat_id, storage["mailing_location"][0], storage["mailing_location"][1])
|
||||
if content_type in (types.ContentType.AUDIO, types.ContentType.VIDEO, types.ContentType.DOCUMENT,
|
||||
types.ContentType.PHOTO):
|
||||
caption = storage.get("mailing_caption")
|
||||
if storage.get("mailing_id"):
|
||||
logging.info("Mailing use file id")
|
||||
obj = storage["mailing_id"]
|
||||
else:
|
||||
logging.info("Mailing upload file")
|
||||
obj = types.InputFile(BytesIO(storage["mailing_data"]), filename=storage.get("mailing_file_name"))
|
||||
|
||||
if content_type == types.ContentType.AUDIO:
|
||||
return (await bot.send_audio(chat_id, audio=obj, caption=caption)).audio.file_id
|
||||
if content_type == types.ContentType.PHOTO:
|
||||
return (await bot.send_photo(chat_id, photo=obj, caption=caption)).photo[-1].file_id
|
||||
if content_type == types.ContentType.VIDEO:
|
||||
return (await bot.send_video(chat_id, video=obj, caption=caption)).video.file_id
|
||||
if content_type == types.ContentType.DOCUMENT:
|
||||
return (await bot.send_document(chat_id, document=obj, caption=caption)).document.file_id
|
||||
|
||||
raise NotImplementedError("Mailing, unknown content type")
|
||||
|
|
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()
|
1082
poetry.lock
generated
Normal file
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.11/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
|
333
server/custom.py
|
@ -1,3 +1,5 @@
|
|||
import asyncio
|
||||
|
||||
from aiogram import Bot as AioBot, Dispatcher
|
||||
from aiogram.dispatcher.webhook import WebhookRequestHandler
|
||||
from aiogram.dispatcher.webhook import SendMessage
|
||||
|
@ -7,20 +9,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, BotStartMessage, BotSecondMessage, MailingUser
|
||||
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 +40,280 @@ 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")
|
||||
bot = db_bot_instance.get()
|
||||
def _tag_uid(bot_id: int, user_id: int) -> str:
|
||||
return f"tag_{bot_id}_{user_id}"
|
||||
|
||||
if message.text and message.text.startswith("/start"):
|
||||
# На команду start нужно ответить, не пересылая сообщение никуда
|
||||
return SendMessage(chat_id=message.chat.id,
|
||||
text=bot.start_text + ServerSettings.append_text())
|
||||
|
||||
super_chat_id = await bot.super_chat_id()
|
||||
def _thread_unique_id(bot_id: int, chat_id: int) -> str:
|
||||
return f"thread_{bot_id}_{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)
|
||||
|
||||
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) ваш идентификатор пользователя записывается в кеш на некоторое время и потом "
|
||||
"удаляется из кеша. Этот идентификатор используется для общения с оператором.\n\n")
|
||||
if bot.enable_additional_info:
|
||||
text += _("При отправке сообщения (кроме команд /start и /security_policy) оператор <b>видит</b> ваши имя "
|
||||
"пользователя, @username и идентификатор пользователя в силу настроек, которые оператор указал при "
|
||||
"создании бота.\n\n")
|
||||
else:
|
||||
# Это супер-чат
|
||||
text += _("В зависимости от ваших настроек конфиденциальности Telegram, оператор может видеть ваш username, "
|
||||
"имя пользователя и другую информацию.\n\n")
|
||||
|
||||
if bot.enable_mailing:
|
||||
text += _("В этом боте включена массовая рассылка в силу настроек, которые оператор указал при создании бота. "
|
||||
"Ваш идентификатор пользователя может быть записан в базу данных на долгое время")
|
||||
else:
|
||||
text += _("В этом боте нет массовой рассылки сообщений")
|
||||
|
||||
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, tag: str = ""):
|
||||
"""Переслать сообщение от пользователя, добавлять к нему 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.from_user.locale:
|
||||
user_info += f" | lang: {message.from_user.locale}"
|
||||
if message.forward_sender_name:
|
||||
user_info += f" | fwd: {message.forward_sender_name}"
|
||||
tag = await _redis.get(_tag_uid(bot.pk, message.from_user.id), encoding="utf-8")
|
||||
if tag:
|
||||
user_info += f" | tag: {tag}"
|
||||
|
||||
# Добавлять информацию в конец текста
|
||||
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())
|
||||
elif tag:
|
||||
# добавлять тег в конец текста
|
||||
if message.content_type == types.ContentType.TEXT and len(message.text) + len(tag) < 4093:
|
||||
new_message = await message.bot.send_message(super_chat_id, message.text + "\n\n" + tag)
|
||||
else:
|
||||
new_message = await message.bot.send_message(super_chat_id, text=tag)
|
||||
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())
|
||||
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 bot.enable_tags:
|
||||
tag = await _redis.get(_tag_uid(bot.pk, message.chat.id), encoding="utf-8")
|
||||
else:
|
||||
tag = ""
|
||||
if tag:
|
||||
tag = str(tag)
|
||||
|
||||
if is_super_group and bot.enable_threads:
|
||||
if bot.enable_thread_interrupt:
|
||||
thread_timeout = ServerSettings.thread_timeout_ms()
|
||||
else:
|
||||
thread_timeout = ServerSettings.redis_timeout_ms()
|
||||
thread_first_message = await _redis.get(_thread_unique_id(bot.pk, message.chat.id))
|
||||
if thread_first_message:
|
||||
# переслать в супер-чат, отвечая на предыдущее сообщение
|
||||
try:
|
||||
if tag:
|
||||
if message.content_type == types.ContentType.TEXT and len(message.text) + len(tag) < 4093:
|
||||
new_message = await message.bot.send_message(
|
||||
super_chat_id,
|
||||
message.text + "\n\n" + tag,
|
||||
reply_to_message_id=int(thread_first_message))
|
||||
else:
|
||||
new_message = await message.copy_to(super_chat_id,
|
||||
reply_to_message_id=int(thread_first_message))
|
||||
new_message_2 = await message.bot.send_message(
|
||||
super_chat_id, reply_to_message_id=new_message.message_id, text=tag)
|
||||
await _redis.set(_message_unique_id(bot.pk, new_message_2.message_id), message.chat.id,
|
||||
pexpire=thread_timeout)
|
||||
else:
|
||||
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=thread_timeout)
|
||||
except exceptions.BadRequest:
|
||||
new_message = await send_user_message(message, super_chat_id, bot, tag)
|
||||
await _redis.set(
|
||||
_thread_unique_id(bot.pk, message.chat.id), new_message.message_id, pexpire=thread_timeout)
|
||||
else:
|
||||
# переслать супер-чат
|
||||
new_message = await send_user_message(message, super_chat_id, bot, tag)
|
||||
await _redis.set(_thread_unique_id(bot.pk, message.chat.id), new_message.message_id,
|
||||
pexpire=thread_timeout)
|
||||
else: # личные сообщения не поддерживают потоки сообщений: просто отправляем сообщение
|
||||
await send_user_message(message, super_chat_id, bot, tag)
|
||||
|
||||
|
||||
async def _increase_count(_bot):
|
||||
_bot.incoming_messages_count = F("incoming_messages_count") + 1
|
||||
await _bot.save(update_fields=["incoming_messages_count"])
|
||||
|
||||
|
||||
async def handle_user_message(message: types.Message, super_chat_id: int, bot):
|
||||
"""Обычный пользователь прислал сообщение в бот, нужно переслать его операторам"""
|
||||
_ = _get_translator(message)
|
||||
is_super_group = super_chat_id < 0
|
||||
|
||||
if bot.enable_mailing:
|
||||
asyncio.create_task(MailingUser.get_or_create(telegram_id=message.chat.id, bot=bot))
|
||||
|
||||
# Проверить, не забанен ли пользователь
|
||||
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.RetryAfter:
|
||||
return SendMessage(chat_id=message.chat.id, text=_("Слишком много сообщений, подождите одну минуту"),
|
||||
reply_to_message_id=message.message_id)
|
||||
except exceptions.TelegramAPIError as err:
|
||||
_logger.error(f"(exception on forwarding) {err}")
|
||||
return
|
||||
|
||||
asyncio.create_task(_increase_count(bot))
|
||||
|
||||
# И отправить пользователю специальный текст, если он указан и если давно не отправляли
|
||||
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 or bot.enable_always_second_message:
|
||||
text_obj = await BotSecondMessage.get_or_none(bot=bot, locale=str(message.from_user.locale))
|
||||
return SendMessage(chat_id=message.chat.id, text=text_obj.text if text_obj else 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>",
|
||||
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=_("Пользователь разбанен"))
|
||||
if bot.enable_tags:
|
||||
if message.text and message.text.startswith("/tag "):
|
||||
tag = message.text.replace("/tag ", "")[:20].strip()
|
||||
if tag:
|
||||
await _redis.set(_tag_uid(bot.pk, chat_id), tag, pexpire=ServerSettings.redis_timeout_ms())
|
||||
return SendMessage(chat_id=message.chat.id, text=_("Тег выставлен"))
|
||||
else:
|
||||
await _redis.delete(_tag_uid(bot.pk, chat_id))
|
||||
return SendMessage(chat_id=message.chat.id, text=_("Тег убран"))
|
||||
|
||||
try:
|
||||
await message.copy_to(chat_id)
|
||||
except (exceptions.MessageError, exceptions.BotBlocked):
|
||||
await message.reply("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>",
|
||||
except (exceptions.MessageError, exceptions.Unauthorized):
|
||||
await message.reply(_("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"),
|
||||
parse_mode="HTML")
|
||||
return
|
||||
else:
|
||||
|
||||
bot.outgoing_messages_count = F("outgoing_messages_count") + 1
|
||||
await bot.save(update_fields=["outgoing_messages_count"])
|
||||
|
||||
elif super_chat_id > 0:
|
||||
# в супер-чате кто-то пишет сообщение сам себе, только для личных сообщений
|
||||
if bot.enable_mailing:
|
||||
asyncio.create_task(MailingUser.get_or_create(telegram_id=message.chat.id, bot=bot))
|
||||
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 == "/start":
|
||||
# На команду start нужно ответить, не пересылая сообщение никуда
|
||||
text_obj = await BotStartMessage.get_or_none(bot=bot, locale=str(message.from_user.locale))
|
||||
text = text_obj.text if text_obj else 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:
|
||||
# Это обычный чат
|
||||
return await handle_user_message(message, super_chat_id, bot)
|
||||
else:
|
||||
# Это супер-чат
|
||||
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 +330,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 +354,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,9 +384,9 @@ class CustomRequestHandler(WebhookRequestHandler):
|
|||
if not bot:
|
||||
return None
|
||||
db_bot_instance.set(bot)
|
||||
dp = Dispatcher(AioBot(bot.token))
|
||||
dp = Dispatcher(AioBot(bot.decrypted_token()))
|
||||
|
||||
dp.register_message_handler(message_handler, content_types=[types.ContentType.TEXT,
|
||||
supported_messages = [types.ContentType.TEXT,
|
||||
types.ContentType.CONTACT,
|
||||
types.ContentType.ANIMATION,
|
||||
types.ContentType.AUDIO,
|
||||
|
@ -116,9 +394,16 @@ class CustomRequestHandler(WebhookRequestHandler):
|
|||
types.ContentType.PHOTO,
|
||||
types.ContentType.STICKER,
|
||||
types.ContentType.VIDEO,
|
||||
types.ContentType.VOICE])
|
||||
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(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
|
||||
|
|