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
|
venv
|
||||||
config.json
|
config.json
|
||||||
*.yaml
|
*.yaml
|
||||||
|
docs/
|
||||||
|
|
2
.flake8
|
@ -1,3 +1,3 @@
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = .git,__pycache__,venv
|
exclude = .git,__pycache__,venv,.venv
|
||||||
max-line-length = 120
|
max-line-length = 120
|
8
.github/workflows/lint.yaml
vendored
|
@ -4,6 +4,8 @@ on: push
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHONUNBUFFERED: 1
|
PYTHONUNBUFFERED: 1
|
||||||
|
POETRY_VERSION: 1.1.2
|
||||||
|
POETRY_VIRTUALENVS_CREATE: "false"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
@ -17,7 +19,7 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install poetry
|
||||||
pip install flake8
|
poetry install
|
||||||
- name: Check flake8
|
- name: Check flake8
|
||||||
run: python -m flake8 .
|
run: flake8 .
|
||||||
|
|
9
.gitignore
vendored
|
@ -5,4 +5,11 @@ venv
|
||||||
__pycache__
|
__pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
config.json
|
config.json
|
||||||
docker-compose-release.yaml
|
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
|
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
|
WORKDIR /app
|
||||||
|
|
||||||
RUN pip install --upgrade pip && \
|
COPY pyproject.toml poetry.lock docker-entrypoint.sh ./
|
||||||
pip install -r requirements.txt
|
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
|
EXPOSE 80
|
||||||
|
|
||||||
|
|
46
README.md
|
@ -1,44 +1,26 @@
|
||||||
# OLGram
|
# OLGram
|
||||||
|
|
||||||
[@olgram](https://t.me/olgrambot) - конструктор ботов обратной связи в Telegram
|
|
||||||
|
|
||||||
[](https://github.com/civsocit/olgram/actions?workflow=Linter)
|
[](https://github.com/civsocit/olgram/actions?workflow=Linter)
|
||||||
[](https://github.com/civsocit/olgram/actions?workflow=Deploy)
|
[](https://github.com/civsocit/olgram/actions?workflow=Deploy)
|
||||||
|
[](https://olgram.readthedocs.io)
|
||||||
|
|
||||||

|
[@OlgramBot](https://t.me/olgrambot) - конструктор ботов обратной связи в Telegram
|
||||||
|
|
||||||
## Возможности и преимущества Olgram Bot
|
Документация: https://olgram.readthedocs.io
|
||||||
|
|
||||||
* **Общение с клиентами**. После подключения бота, вы сможете общаться с вашими пользователями бота через диалог с
|
|
||||||
ботом, либо подключенный отдельно чат, где может находиться ваш колл-центр.
|
|
||||||
* **Все типы сообщений**. Livegram боты поддерживают все типы сообщений — текст, фото, видео, голосовые сообщения и
|
|
||||||
стикеры.
|
|
||||||
* **Open-source**. В отличие от известного проекта Livegram код нашего конструктора полностью открыт.
|
|
||||||
* **Self-hosted**. Вы можете развернуть свой собственный конструктор, если не доверяете нашему.
|
|
||||||
* **Безопасность**. В отличие от Livegram, мы не храним сообщения, которые вы отправляете в бот. А наши сервера
|
|
||||||
располагаются в Германии, что делает проект неподконтрольным российским властям.
|
|
||||||
|
|
||||||
|
|
||||||
По любым вопросам, связанным с Olgram, пишите в наш бот обратной связи
|
**Olgram** [@OlgramBot](https://t.me/olgrambot) это конструктор, который позволяет создавать боты обратной связи
|
||||||
[@civsocit_feedback_bot](https://t.me/civsocit_feedback_bot)
|
в Telegram. После подключения к Olgram пользователи вашего бота смогут писать сообщения, которые будут
|
||||||
|
пересылаться вам в чат, где вы сможете на них ответить.
|
||||||
|
|
||||||
### Для разработчиков: сборка и запуск проекта
|
Такие боты могут вам пригодиться, например:
|
||||||
|
|
||||||
Вам потребуется собственный VPS или любой хост со статическим адресом или доменом.
|
*Пример 1.* Вы администрируете Telegram-канал и хотите дать своим подписчикам возможность связаться с вами,
|
||||||
* Создайте файл .env и заполните его по образцу example.env. Вам нужно заполнить переменные:
|
но не хотите оставлять свои личные контакты. Тогда вы можете создать бота обратной связи: подписчики будут писать
|
||||||
* BOT_TOKEN - токен нового бота, получить у [@botfather](https://t.me/botfather)
|
боту, вы будете отвечать через бота анонимно.
|
||||||
* POSTGRES_PASSWORD - любой случайный пароль
|
|
||||||
* WEBHOOK_HOST - IP адрес или доменное имя сервера, на котором запускается проект
|
|
||||||
* Сохраните файл docker-compose.yaml и соберите его:
|
|
||||||
```
|
|
||||||
sudo docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
В docker-compose.yaml минимальная конфигурация. Для использования в серьёзных проектах мы советуем:
|
*Пример 2.* Вы организуете небольшой call-центр в Telegram или группу технической поддержки. С помощью бота обратной
|
||||||
* Приобрести домен и настроить его на свой хост
|
связи вы можете принимать заявки от пользователей в общий чат ваших специалистов, обсуждать эти заявки и отвечать
|
||||||
* Наладить реверс-прокси и автоматическое обновление сертификатов - например, с помощью
|
пользователям прямо из этого чата.
|
||||||
[Traefik](https://github.com/traefik/traefik)
|
|
||||||
* Скрыть IP сервера с помощью [Cloudflire](https://www.cloudflare.com), чтобы пользователи ботов не могли найти IP адрес
|
|
||||||
хоста по Webhook бота.
|
|
||||||
|
|
||||||
Пример более сложной конфигурации есть в файле docker-compose-full.yaml
|
Читайте больше: https://olgram.readthedocs.io
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
# Конфигурация, удобная для разработки в PyCharm: бот запускается без docker, порты postgres и redis открыты на localhost
|
||||||
|
# Не используйте её в production!
|
||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: kartoza/postgis
|
image: postgres:14
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=test_user
|
- POSTGRES_USER=test_user
|
||||||
- POSTGRES_PASSWORD=test_passwd
|
- POSTGRES_PASSWORD=test_passwd
|
||||||
|
@ -11,7 +13,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- database:/var/lib/postgresql/data
|
- database:/var/lib/postgresql/data
|
||||||
redis:
|
redis:
|
||||||
image: 'bitnami/redis:latest'
|
image: 'bitnami/redis:6.2.7'
|
||||||
environment:
|
environment:
|
||||||
- ALLOW_EMPTY_PASSWORD=yes
|
- ALLOW_EMPTY_PASSWORD=yes
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
# Пример сложной конфигурации сервера: реверс-прокси, автоматическое обновление github
|
||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres
|
image: postgres:13.4
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
- release.env
|
- release.env
|
||||||
|
@ -9,8 +10,10 @@ services:
|
||||||
- database:/var/lib/postgresql/data
|
- database:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
- traefik
|
- traefik
|
||||||
|
labels:
|
||||||
|
- 'com.centurylinklabs.watchtower.enable="false"'
|
||||||
redis:
|
redis:
|
||||||
image: 'bitnami/redis:latest'
|
image: 'bitnami/redis:6.2.7'
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- ALLOW_EMPTY_PASSWORD=yes
|
- ALLOW_EMPTY_PASSWORD=yes
|
||||||
|
@ -20,6 +23,8 @@ services:
|
||||||
- release.env
|
- release.env
|
||||||
networks:
|
networks:
|
||||||
- traefik
|
- traefik
|
||||||
|
labels:
|
||||||
|
- 'com.centurylinklabs.watchtower.enable="false"'
|
||||||
olgram:
|
olgram:
|
||||||
image: ghcr.io/civsocit/olgram/bot:stable
|
image: ghcr.io/civsocit/olgram/bot:stable
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
@ -71,6 +76,8 @@ services:
|
||||||
- --certificatesresolvers.le.acme.tlschallenge=false
|
- --certificatesresolvers.le.acme.tlschallenge=false
|
||||||
- --certificatesresolvers.le.acme.httpchallenge=true
|
- --certificatesresolvers.le.acme.httpchallenge=true
|
||||||
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
|
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
|
||||||
|
labels:
|
||||||
|
- 'com.centurylinklabs.watchtower.enable="false"'
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
database:
|
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'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
@ -8,7 +9,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- database:/var/lib/postgresql/data
|
- database:/var/lib/postgresql/data
|
||||||
redis:
|
redis:
|
||||||
image: 'bitnami/redis:latest'
|
image: 'bitnami/redis:6.2.7'
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- ALLOW_EMPTY_PASSWORD=yes
|
- ALLOW_EMPTY_PASSWORD=yes
|
||||||
|
|
|
@ -5,10 +5,11 @@ if [ ! -z "${CUSTOM_CERT}" ]; then
|
||||||
echo "Use custom certificate"
|
echo "Use custom certificate"
|
||||||
if [ ! -f /cert/private.key ]; then
|
if [ ! -f /cert/private.key ]; then
|
||||||
echo "Generate new certificate"
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sleep 10
|
sleep 10
|
||||||
aerich upgrade
|
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_USER=olgram
|
||||||
POSTGRES_PASSWORD=SOME_RANDOM_PASSWORD_HERE # example: x2y0n27ihiez93kmzj82
|
|
||||||
|
# example: x2y0n27ihiez93kmzj82 (without quotes!)
|
||||||
|
POSTGRES_PASSWORD=SOME_RANDOM_PASSWORD_HERE
|
||||||
|
|
||||||
POSTGRES_DB=olgram
|
POSTGRES_DB=olgram
|
||||||
POSTGRES_HOST=postgres
|
POSTGRES_HOST=postgres
|
||||||
|
|
||||||
WEBHOOK_HOST=YOUR_HOST_HERE # example: 11.143.142.140 or my_domain.com
|
# example: i7flci0mx4z5patxnl6m (without quotes!)
|
||||||
WEBHOOK_PORT=8443 # allowed: 80, 443, 8080, 8443
|
TOKEN_ENCRYPTION_KEY=SOME_RANDOM_PASSWORD_HERE
|
||||||
CUSTOM_CERT=true # use that if you don't set up your own domain and let's encrypt certificate
|
|
||||||
|
# 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
|
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 "
|
41
main.py
|
@ -1,34 +1,38 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import argparse
|
||||||
from tortoise import Tortoise
|
from tortoise import Tortoise
|
||||||
|
|
||||||
from olgram.router import dp
|
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
|
from server.custom import init_redis
|
||||||
|
|
||||||
|
import olgram.commands.menu # noqa: F401
|
||||||
import olgram.commands.bots # noqa: F401
|
import olgram.commands.bots # noqa: F401
|
||||||
import olgram.commands.start # noqa: F401
|
import olgram.commands.start # noqa: F401
|
||||||
import olgram.commands.menu # noqa: F401
|
|
||||||
import olgram.commands.bot_actions # 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
|
from server.server import main as server_main
|
||||||
|
|
||||||
import logging
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
async def init_database():
|
async def init_database():
|
||||||
await Tortoise.init(config=TORTOISE_ORM)
|
await Tortoise.init(config=TORTOISE_ORM)
|
||||||
|
|
||||||
|
|
||||||
async def init_olgram():
|
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
|
from aiogram.types import BotCommand
|
||||||
await bot.set_my_commands(
|
await bot.set_my_commands(
|
||||||
[
|
[
|
||||||
BotCommand("start", "Запустить бота"),
|
BotCommand("start", _("Запустить бота")),
|
||||||
BotCommand("addbot", "Добавить бот"),
|
BotCommand("addbot", _("Добавить бот")),
|
||||||
BotCommand("mybots", "Управление ботами"),
|
BotCommand("mybots", _("Управление ботами")),
|
||||||
BotCommand("help", "Справка")
|
BotCommand("help", _("Справка"))
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,14 +44,21 @@ async def initialization():
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
parser = argparse.ArgumentParser("Olgram bot and feedback server")
|
||||||
Classic polling
|
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 = asyncio.get_event_loop()
|
||||||
loop.run_until_complete(initialization())
|
loop.run_until_complete(initialization())
|
||||||
|
|
||||||
loop.create_task(dp.start_polling())
|
if not args.onlyserver:
|
||||||
loop.create_task(server_main().start())
|
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()
|
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 import types
|
||||||
from aiogram.utils.exceptions import TelegramAPIError
|
from asyncio import sleep
|
||||||
from olgram.models.models import Bot
|
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 server.server import unregister_token
|
||||||
|
from locales.locale import _
|
||||||
|
|
||||||
|
|
||||||
async def delete_bot(bot: Bot, call: types.CallbackQuery):
|
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 bot.delete()
|
||||||
await call.answer("Бот удалён")
|
await call.answer(_("Бот удалён"))
|
||||||
try:
|
try:
|
||||||
await call.message.delete()
|
await call.message.delete()
|
||||||
except TelegramAPIError:
|
except exceptions.TelegramAPIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def reset_bot_text(bot: Bot, call: types.CallbackQuery):
|
async def reset_bot_text(bot: Bot, call: types.CallbackQuery, state):
|
||||||
"""
|
"""
|
||||||
Пользователь решил сбросить текст бота к default
|
Пользователь решил сбросить текст бота к default
|
||||||
:param bot:
|
:param bot:
|
||||||
:param call:
|
:param call:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
bot.start_text = bot._meta.fields_map['start_text'].default
|
async with state.proxy() as proxy:
|
||||||
await bot.save()
|
lang = proxy.get("lang", "none")
|
||||||
await call.answer("Текст сброшен")
|
if lang == "none":
|
||||||
|
await BotStartMessage.filter(bot=bot).delete()
|
||||||
|
bot.start_text = bot._meta.fields_map['start_text'].default
|
||||||
|
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):
|
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":
|
if chat == "personal":
|
||||||
bot.group_chat = None
|
bot.group_chat = None
|
||||||
await bot.save()
|
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
|
return
|
||||||
|
|
||||||
chat_obj = await bot.group_chats.filter(id=chat).first()
|
chat_obj = await bot.group_chats.filter(id=chat).first()
|
||||||
if not chat_obj:
|
if not chat_obj:
|
||||||
await call.answer("Нельзя привязать бота к этому чату")
|
await call.answer(_("Нельзя привязать бота к этому чату"))
|
||||||
return
|
return
|
||||||
bot.group_chat = chat_obj
|
bot.group_chat = chat_obj
|
||||||
await bot.save()
|
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 textwrap import dedent
|
||||||
|
|
||||||
from olgram.models.models import Bot, User
|
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 olgram.commands.menu import send_bots_menu
|
||||||
from server.server import register_token
|
from server.server import register_token
|
||||||
|
from locales.locale import _
|
||||||
|
|
||||||
from olgram.router import dp
|
from olgram.router import dp
|
||||||
|
|
||||||
|
@ -36,12 +37,17 @@ async def add_bot(message: types.Message, state: FSMContext):
|
||||||
"""
|
"""
|
||||||
Команда /addbot (добавить бота)
|
Команда /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()
|
bot_count = await Bot.filter(owner__telegram_id=message.from_user.id).count()
|
||||||
if bot_count >= OlgramSettings.max_bots_per_user():
|
if bot_count >= max_bot_count:
|
||||||
await message.answer("У вас уже слишком много ботов.")
|
await message.answer(_("У вас уже слишком много ботов. Удалите какой-нибудь свой бот из Olgram"
|
||||||
|
"(/mybots -> (Выбрать бота) -> Удалить бот)"))
|
||||||
return
|
return
|
||||||
|
|
||||||
await message.answer(dedent("""
|
await message.answer(dedent(_("""
|
||||||
Чтобы подключить бот, вам нужно выполнить три действия:
|
Чтобы подключить бот, вам нужно выполнить три действия:
|
||||||
|
|
||||||
1. Перейдите в бот @BotFather, нажмите START и отправьте команду /newbot
|
1. Перейдите в бот @BotFather, нажмите START и отправьте команду /newbot
|
||||||
|
@ -49,7 +55,7 @@ async def add_bot(message: types.Message, state: FSMContext):
|
||||||
3. После создания бота перешлите ответное сообщение в этот бот или скопируйте и пришлите token бота.
|
3. После создания бота перешлите ответное сообщение в этот бот или скопируйте и пришлите token бота.
|
||||||
|
|
||||||
Важно: не подключайте боты, которые используются в других сервисах (Manybot, Chatfuel, Livegram и других).
|
Важно: не подключайте боты, которые используются в других сервисах (Manybot, Chatfuel, Livegram и других).
|
||||||
"""))
|
""")))
|
||||||
await state.set_state("add_bot")
|
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)
|
token = re.findall(token_pattern, message.text)
|
||||||
|
|
||||||
async def on_invalid_token():
|
async def on_invalid_token():
|
||||||
await message.answer(dedent("""
|
await message.answer(dedent(_("""
|
||||||
Это не токен бота.
|
Это не токен бота.
|
||||||
|
|
||||||
Токен выглядит вот так: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12
|
Токен выглядит вот так: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12
|
||||||
"""))
|
""")))
|
||||||
|
|
||||||
async def on_dummy_token():
|
async def on_dummy_token():
|
||||||
await message.answer(dedent("""
|
await message.answer(dedent(_("""
|
||||||
Не удалось запустить этого бота: неверный токен
|
Не удалось запустить этого бота: неверный токен
|
||||||
"""))
|
""")))
|
||||||
|
|
||||||
async def on_unknown_error():
|
async def on_unknown_error():
|
||||||
await message.answer(dedent("""
|
await message.answer(dedent(_("""
|
||||||
Не удалось запустить этого бота: непредвиденная ошибка
|
Не удалось запустить этого бота: непредвиденная ошибка
|
||||||
"""))
|
""")))
|
||||||
|
|
||||||
async def on_duplication_bot():
|
async def on_duplication_bot():
|
||||||
await message.answer(dedent("""
|
await message.answer(dedent(_("""
|
||||||
Такой бот уже есть в базе данных
|
Такой бот уже есть в базе данных
|
||||||
"""))
|
""")))
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
return await on_invalid_token()
|
return await on_invalid_token()
|
||||||
|
@ -98,8 +104,12 @@ async def bot_added(message: types.Message, state: FSMContext):
|
||||||
except TelegramAPIError:
|
except TelegramAPIError:
|
||||||
return await on_unknown_error()
|
return await on_unknown_error()
|
||||||
|
|
||||||
user, _ = await User.get_or_create(telegram_id=message.from_user.id)
|
if token == BotSettings.token():
|
||||||
bot = Bot(token=token, owner=user, name=test_bot_info.username, super_chat_id=message.from_user.id)
|
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:
|
try:
|
||||||
await bot.save()
|
await bot.save()
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
|
@ -109,5 +119,5 @@ async def bot_added(message: types.Message, state: FSMContext):
|
||||||
await bot.delete()
|
await bot.delete()
|
||||||
return await on_unknown_error()
|
return await on_unknown_error()
|
||||||
|
|
||||||
await message.answer("Бот добавлен! Список ваших ботов: /mybots")
|
await message.answer(_("Бот добавлен! Список ваших ботов: /mybots"))
|
||||||
await state.reset_state()
|
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 olgram.router import dp
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
from aiogram import types, Bot as AioBot
|
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.dispatcher import FSMContext
|
||||||
from aiogram.utils.callback_data import CallbackData
|
from aiogram.utils.callback_data import CallbackData
|
||||||
from textwrap import dedent
|
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 olgram.commands import bot_actions
|
||||||
|
from locales.locale import _
|
||||||
|
|
||||||
import typing as ty
|
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)
|
user = await User.get_or_none(telegram_id=user_id)
|
||||||
bots = await Bot.filter(owner=user)
|
bots = await Bot.filter(owner=user)
|
||||||
if not bots:
|
if not bots:
|
||||||
await AioBot.get_current().send_message(chat_id, dedent("""
|
await AioBot.get_current().send_message(chat_id, dedent(_("""
|
||||||
У вас нет добавленных ботов.
|
У вас нет добавленных ботов.
|
||||||
|
|
||||||
Отправьте команду /addbot, чтобы добавить бот.
|
Отправьте команду /addbot, чтобы добавить бот.
|
||||||
"""))
|
""")))
|
||||||
return
|
return
|
||||||
|
|
||||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
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))
|
chat=empty))
|
||||||
)
|
)
|
||||||
|
|
||||||
text = "Ваши боты"
|
text = _("Ваши боты")
|
||||||
if call:
|
if call:
|
||||||
await edit_or_create(call, text, keyboard)
|
await edit_or_create(call, text, keyboard)
|
||||||
else:
|
else:
|
||||||
|
@ -63,26 +66,33 @@ async def send_chats_menu(bot: Bot, call: types.CallbackQuery):
|
||||||
)
|
)
|
||||||
if chats:
|
if chats:
|
||||||
keyboard.insert(
|
keyboard.insert(
|
||||||
types.InlineKeyboardButton(text="Личные сообщения",
|
types.InlineKeyboardButton(text=_("Личные сообщения"),
|
||||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
|
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
|
||||||
chat="personal"))
|
chat="personal"))
|
||||||
)
|
)
|
||||||
|
keyboard.insert(
|
||||||
|
types.InlineKeyboardButton(text=_("❗️ Выйти из всех чатов"),
|
||||||
|
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
|
||||||
|
chat="leave"))
|
||||||
|
)
|
||||||
keyboard.insert(
|
keyboard.insert(
|
||||||
types.InlineKeyboardButton(text="<< Назад",
|
types.InlineKeyboardButton(text=_("<< Назад"),
|
||||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty,
|
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty,
|
||||||
chat=empty))
|
chat=empty))
|
||||||
)
|
)
|
||||||
|
|
||||||
if not chats:
|
if not chats:
|
||||||
text = dedent(f"""
|
text = dedent(_("""
|
||||||
Этот бот не добавлен в чаты, поэтому все сообщения будут приходить вам в бот.
|
Этот бот не добавлен в чаты, поэтому все сообщения будут приходить вам в бот.
|
||||||
Чтобы подключить чат — просто добавьте бот @{bot.name} в чат.
|
Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню ещё раз и выберите добавленный чат.
|
||||||
""")
|
Если ваш бот состоял в групповом чате до того, как его добавили в Olgram - удалите бота из чата и добавьте
|
||||||
|
снова.
|
||||||
|
""")).format(bot.name)
|
||||||
else:
|
else:
|
||||||
text = dedent(f"""
|
text = dedent(_("""
|
||||||
В этом разделе вы можете привязать бота @{bot.name} к чату.
|
В этом разделе вы можете привязать бота @{0} к чату.
|
||||||
Выберите чат, куда бот будет пересылать сообщения.
|
Выберите чат, куда бот будет пересылать сообщения.
|
||||||
""")
|
""")).format(bot.name)
|
||||||
|
|
||||||
await edit_or_create(call, text, keyboard)
|
await edit_or_create(call, text, keyboard)
|
||||||
|
|
||||||
|
@ -91,76 +101,421 @@ async def send_bot_menu(bot: Bot, call: types.CallbackQuery):
|
||||||
await call.answer()
|
await call.answer()
|
||||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||||
keyboard.insert(
|
keyboard.insert(
|
||||||
types.InlineKeyboardButton(text="Текст",
|
types.InlineKeyboardButton(text=_("Текст"),
|
||||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text",
|
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text",
|
||||||
chat=empty))
|
chat=empty))
|
||||||
)
|
)
|
||||||
keyboard.insert(
|
keyboard.insert(
|
||||||
types.InlineKeyboardButton(text="Чат",
|
types.InlineKeyboardButton(text=_("Чат"),
|
||||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="chat",
|
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="chat",
|
||||||
chat=empty))
|
chat=empty))
|
||||||
)
|
)
|
||||||
keyboard.insert(
|
keyboard.insert(
|
||||||
types.InlineKeyboardButton(text="Удалить бот",
|
types.InlineKeyboardButton(text=_("Удалить бот"),
|
||||||
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="delete",
|
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="delete",
|
||||||
chat=empty))
|
chat=empty))
|
||||||
)
|
)
|
||||||
keyboard.insert(
|
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))
|
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"""
|
await edit_or_create(call, dedent(_("""
|
||||||
Управление ботом @{bot.name}.
|
Управление ботом @{0}.
|
||||||
|
|
||||||
Если у вас возникли вопросы по настройке бота, то посмотрите нашу справку /help или напишите нам
|
Если у вас возникли вопросы по настройке бота, то посмотрите нашу справку /help или напишите нам
|
||||||
@civsocit_feedback_bot
|
@civsocit_feedback_bot
|
||||||
"""), reply_markup=keyboard)
|
""")).format(bot.name), reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
async def send_bot_delete_menu(bot: Bot, call: types.CallbackQuery):
|
async def send_bot_delete_menu(bot: Bot, call: types.CallbackQuery):
|
||||||
await call.answer()
|
await call.answer()
|
||||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||||
keyboard.insert(
|
keyboard.insert(
|
||||||
types.InlineKeyboardButton(text="Да, удалить бот",
|
types.InlineKeyboardButton(text=_("Да, удалить бот"),
|
||||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="delete_yes",
|
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="delete_yes",
|
||||||
chat=empty))
|
chat=empty))
|
||||||
)
|
)
|
||||||
keyboard.insert(
|
keyboard.insert(
|
||||||
types.InlineKeyboardButton(text="<< Назад",
|
types.InlineKeyboardButton(text=_("<< Назад"),
|
||||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
||||||
)
|
)
|
||||||
|
|
||||||
await edit_or_create(call, dedent(f"""
|
await edit_or_create(call, dedent(_("""
|
||||||
Вы уверены, что хотите удалить бота @{bot.name}?
|
Вы уверены, что хотите удалить бота @{0}?
|
||||||
"""), reply_markup=keyboard)
|
""")).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:
|
if call:
|
||||||
await call.answer()
|
await call.answer()
|
||||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||||
keyboard.insert(
|
keyboard.insert(
|
||||||
types.InlineKeyboardButton(text="Сбросить текст",
|
types.InlineKeyboardButton(text=_("<< Назад"),
|
||||||
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="reset_text",
|
|
||||||
chat=empty))
|
|
||||||
)
|
|
||||||
keyboard.insert(
|
|
||||||
types.InlineKeyboardButton(text="<< Завершить редактирование",
|
|
||||||
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
|
||||||
)
|
)
|
||||||
|
|
||||||
text = dedent("""
|
text = dedent(_("""
|
||||||
Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту {0}
|
Статистика по боту @{0}
|
||||||
команду /start
|
|
||||||
|
Входящих сообщений: <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>
|
<pre>
|
||||||
{1}
|
{1}
|
||||||
</pre>
|
</pre>
|
||||||
Отправьте сообщение, чтобы изменить текст.
|
Отправьте какую-нибудь фразу (например: "Ваш заказ готов, ожидайте!"), чтобы добавить её в шаблон.
|
||||||
""")
|
Чтобы удалить шаблон из списка, отправьте его номер в списке (например, 4)
|
||||||
text = text.format(bot.name, bot.start_text)
|
"""))
|
||||||
|
|
||||||
|
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:
|
if call:
|
||||||
await edit_or_create(call, text, keyboard, parse_mode="HTML")
|
await edit_or_create(call, text, keyboard, parse_mode="HTML")
|
||||||
else:
|
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 def start_text_received(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as proxy:
|
async with state.proxy() as proxy:
|
||||||
bot_id = proxy.get("bot_id")
|
bot_id = proxy.get("bot_id")
|
||||||
|
lang = proxy.get("lang", "none")
|
||||||
|
|
||||||
bot = await Bot.get_or_none(pk=bot_id)
|
bot = await Bot.get_or_none(pk=bot_id)
|
||||||
bot.start_text = message.text
|
if lang == "none":
|
||||||
await bot.save()
|
bot.start_text = message.html_text
|
||||||
await send_bot_text_menu(bot, chat_id=message.chat.id)
|
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="*")
|
@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_id = callback_data.get("bot_id")
|
||||||
bot = await Bot.get_or_none(id=bot_id)
|
bot = await Bot.get_or_none(id=bot_id)
|
||||||
if not bot or (await bot.owner).telegram_id != call.from_user.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
|
return
|
||||||
|
|
||||||
if level == "1":
|
if level == "1":
|
||||||
|
await state.reset_state()
|
||||||
return await send_bot_menu(bot, call)
|
return await send_bot_menu(bot, call)
|
||||||
|
|
||||||
operation = callback_data.get("operation")
|
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)
|
return await send_chats_menu(bot, call)
|
||||||
if operation == "delete":
|
if operation == "delete":
|
||||||
return await send_bot_delete_menu(bot, call)
|
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":
|
if operation == "text":
|
||||||
await state.set_state("wait_start_text")
|
await state.set_state("wait_start_text")
|
||||||
async with state.proxy() as proxy:
|
async with state.proxy() as proxy:
|
||||||
proxy["bot_id"] = bot.id
|
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 level == "3":
|
||||||
if operation == "delete_yes":
|
if operation == "delete_yes":
|
||||||
return await bot_actions.delete_bot(bot, call)
|
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":
|
if operation == "chat":
|
||||||
return await bot_actions.select_chat(bot, call, callback_data.get("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":
|
if operation == "reset_text":
|
||||||
await bot_actions.reset_bot_text(bot, call)
|
await bot_actions.reset_bot_text(bot, call, state)
|
||||||
return await send_bot_text_menu(bot, call)
|
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 aiogram.dispatcher import FSMContext
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from olgram.settings import OlgramSettings
|
from olgram.settings import OlgramSettings
|
||||||
|
from olgram.utils.permissions import public
|
||||||
|
from locales.locale import _
|
||||||
|
|
||||||
from olgram.router import dp
|
from olgram.router import dp
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=["start"], state="*")
|
@dp.message_handler(commands=["start"], state="*")
|
||||||
|
@public()
|
||||||
async def start(message: types.Message, state: FSMContext):
|
async def start(message: types.Message, state: FSMContext):
|
||||||
"""
|
"""
|
||||||
Команда /start
|
Команда /start
|
||||||
"""
|
"""
|
||||||
await state.reset_state()
|
await state.reset_state()
|
||||||
|
|
||||||
# TODO: locale
|
await message.answer(dedent(_("""
|
||||||
|
Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее \
|
||||||
await message.answer(dedent("""
|
<a href="https://olgram.readthedocs.io">читайте здесь</a>. Следите за обновлениями \
|
||||||
Olgram Bot — это конструктор ботов обратной связи в Telegram.
|
<a href="https://t.me/civsoc_it">здесь</a>.
|
||||||
|
|
||||||
Используйте эти команды, чтобы управлять этим ботом:
|
Используйте эти команды, чтобы управлять этим ботом:
|
||||||
|
|
||||||
|
@ -28,20 +31,26 @@ async def start(message: types.Message, state: FSMContext):
|
||||||
/mybots - управление ботами
|
/mybots - управление ботами
|
||||||
|
|
||||||
/help - помощь
|
/help - помощь
|
||||||
"""))
|
""")), parse_mode="html", disable_web_page_preview=True)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=["help"], state="*")
|
@dp.message_handler(commands=["help"], state="*")
|
||||||
|
@public()
|
||||||
async def help(message: types.Message, state: FSMContext):
|
async def help(message: types.Message, state: FSMContext):
|
||||||
"""
|
"""
|
||||||
Команда /help
|
Команда /help
|
||||||
"""
|
"""
|
||||||
await message.answer(dedent(f"""
|
await message.answer(dedent(_("""
|
||||||
О проекте https://telegra.ph/Olgram-09-15
|
Читайте инструкции на нашем сайте https://olgram.readthedocs.io
|
||||||
|
Техническая поддержка: @civsocit_feedback_bot
|
||||||
|
Версия {0}
|
||||||
|
""")).format(OlgramSettings.version()))
|
||||||
|
|
||||||
Репозиторий https://github.com/civsocit/olgram
|
|
||||||
|
|
||||||
Поддержка: @civsocit_feedback_bot
|
@dp.message_handler(commands=["chatid"], state="*")
|
||||||
|
@public()
|
||||||
Версия {OlgramSettings.version()}
|
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 tortoise import fields
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from textwrap import dedent
|
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):
|
class Bot(Model):
|
||||||
id = fields.IntField(pk=True)
|
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")
|
owner = fields.ForeignKeyField("models.User", related_name="bots")
|
||||||
name = fields.CharField(max_length=33)
|
name = fields.CharField(max_length=33)
|
||||||
code = fields.UUIDField(default=uuid4, index=True)
|
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,
|
group_chats = fields.ManyToManyField("models.GroupChat", related_name="bots", on_delete=fields.relational.CASCADE,
|
||||||
null=True)
|
null=True)
|
||||||
|
@ -21,24 +39,87 @@ class Bot(Model):
|
||||||
on_delete=fields.relational.CASCADE,
|
on_delete=fields.relational.CASCADE,
|
||||||
null=True)
|
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):
|
async def super_chat_id(self):
|
||||||
group_chat = await self.group_chat
|
group_chat = await self.group_chat
|
||||||
if group_chat:
|
if group_chat:
|
||||||
return group_chat.chat_id
|
return group_chat.chat_id
|
||||||
return (await self.owner).telegram_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:
|
class Meta:
|
||||||
table = 'bot'
|
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):
|
class User(Model):
|
||||||
id = fields.IntField(pk=True)
|
id = fields.IntField(pk=True)
|
||||||
telegram_id = fields.BigIntField(index=True, unique=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:
|
class Meta:
|
||||||
table = 'user'
|
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):
|
class GroupChat(Model):
|
||||||
id = fields.IntField(pk=True)
|
id = fields.IntField(pk=True)
|
||||||
chat_id = fields.BigIntField(index=True, unique=True)
|
chat_id = fields.BigIntField(index=True, unique=True)
|
||||||
|
@ -46,3 +127,29 @@ class GroupChat(Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table = 'group_chat'
|
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 dotenv import load_dotenv
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
import os
|
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()
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: рефакторинг, использовать какой-нибудь lazy-config вместо своих костылей
|
||||||
|
|
||||||
class AbstractSettings(ABC):
|
class AbstractSettings(ABC):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_env(cls, parameter: str, allow_none: bool = False) -> str:
|
def _get_env(cls, parameter: str, allow_none: bool = False) -> str:
|
||||||
parameter = os.getenv(parameter, None)
|
parameter_v = os.getenv(parameter, None)
|
||||||
if not parameter and not allow_none:
|
if not parameter_v and not allow_none:
|
||||||
raise ValueError(f"{parameter} not defined in ENV")
|
raise ValueError(f"{parameter} not defined in ENV")
|
||||||
return parameter
|
return parameter_v
|
||||||
|
|
||||||
|
|
||||||
class OlgramSettings(AbstractSettings):
|
class OlgramSettings(AbstractSettings):
|
||||||
|
@ -22,11 +28,31 @@ class OlgramSettings(AbstractSettings):
|
||||||
Максимальное количество ботов у одного пользователя
|
Максимальное количество ботов у одного пользователя
|
||||||
:return: int
|
:return: int
|
||||||
"""
|
"""
|
||||||
return 5
|
return 10
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def max_bots_per_user_promo(cls) -> int:
|
||||||
|
"""
|
||||||
|
Максимальное количество ботов у одного пользователя с промо-доступом
|
||||||
|
:return: int
|
||||||
|
"""
|
||||||
|
return 25
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def version(cls):
|
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):
|
class ServerSettings(AbstractSettings):
|
||||||
|
@ -38,10 +64,6 @@ class ServerSettings(AbstractSettings):
|
||||||
def hook_port(cls) -> int:
|
def hook_port(cls) -> int:
|
||||||
return int(cls._get_env("WEBHOOK_PORT"))
|
return int(cls._get_env("WEBHOOK_PORT"))
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def app_host(cls) -> str:
|
|
||||||
return "olgram"
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def app_port(cls) -> int:
|
def app_port(cls) -> int:
|
||||||
return 80
|
return 80
|
||||||
|
@ -71,9 +93,24 @@ class ServerSettings(AbstractSettings):
|
||||||
def append_text(cls) -> str:
|
def append_text(cls) -> str:
|
||||||
return "\n\nЭтот бот создан с помощью @OlgramBot"
|
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):
|
class BotSettings(AbstractSettings):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@lru_cache
|
||||||
def token(cls) -> str:
|
def token(cls) -> str:
|
||||||
"""
|
"""
|
||||||
Токен olgram бота
|
Токен olgram бота
|
||||||
|
@ -81,6 +118,14 @@ class BotSettings(AbstractSettings):
|
||||||
"""
|
"""
|
||||||
return cls._get_env("BOT_TOKEN")
|
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):
|
class DatabaseSettings(AbstractSettings):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -99,6 +144,12 @@ class DatabaseSettings(AbstractSettings):
|
||||||
def host(cls) -> str:
|
def host(cls) -> str:
|
||||||
return cls._get_env("POSTGRES_HOST")
|
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 = {
|
TORTOISE_ORM = {
|
||||||
"connections": {"default": f'postgres://{DatabaseSettings.user()}:{DatabaseSettings.password()}'
|
"connections": {"default": f'postgres://{DatabaseSettings.user()}:{DatabaseSettings.password()}'
|
||||||
|
@ -109,4 +160,6 @@ TORTOISE_ORM = {
|
||||||
"default_connection": "default",
|
"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.types import Message, CallbackQuery, InlineKeyboardMarkup
|
||||||
from aiogram.utils.exceptions import TelegramAPIError
|
from aiogram.utils.exceptions import TelegramAPIError
|
||||||
|
from aiogram import types, Bot as AioBot
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
@ -22,8 +25,39 @@ async def edit_or_create(call: CallbackQuery, message: str,
|
||||||
parse_mode=parse_mode)
|
parse_mode=parse_mode)
|
||||||
|
|
||||||
|
|
||||||
def button_text_limit(data: str) -> str:
|
def wrap(data: str, max_len: int) -> str:
|
||||||
max_len = 30
|
|
||||||
if len(data) > max_len:
|
if len(data) > max_len:
|
||||||
data = data[:max_len-4] + "..."
|
data = data[:max_len-4] + "..."
|
||||||
return data
|
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
|
|
363
server/custom.py
|
@ -1,3 +1,5 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from aiogram import Bot as AioBot, Dispatcher
|
from aiogram import Bot as AioBot, Dispatcher
|
||||||
from aiogram.dispatcher.webhook import WebhookRequestHandler
|
from aiogram.dispatcher.webhook import WebhookRequestHandler
|
||||||
from aiogram.dispatcher.webhook import SendMessage
|
from aiogram.dispatcher.webhook import SendMessage
|
||||||
|
@ -7,20 +9,28 @@ from contextvars import ContextVar
|
||||||
from aiohttp.web_exceptions import HTTPNotFound
|
from aiohttp.web_exceptions import HTTPNotFound
|
||||||
from aioredis.commands import create_redis_pool
|
from aioredis.commands import create_redis_pool
|
||||||
from aioredis import Redis
|
from aioredis import Redis
|
||||||
|
from tortoise.expressions import F
|
||||||
import logging
|
import logging
|
||||||
import typing as ty
|
import typing as ty
|
||||||
from olgram.settings import ServerSettings
|
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 = logging.getLogger(__name__)
|
||||||
|
_logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
db_bot_instance: ContextVar[Bot] = ContextVar('db_bot_instance')
|
db_bot_instance: ContextVar[Bot] = ContextVar('db_bot_instance')
|
||||||
|
|
||||||
_redis: ty.Optional[Redis] = None
|
_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():
|
async def init_redis():
|
||||||
global _redis
|
global _redis
|
||||||
_redis = await create_redis_pool(ServerSettings.redis_path())
|
_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}"
|
return f"{bot_id}_{message_id}"
|
||||||
|
|
||||||
|
|
||||||
async def message_handler(message, *args, **kwargs):
|
def _tag_uid(bot_id: int, user_id: int) -> str:
|
||||||
_logger.info("message handler")
|
return f"tag_{bot_id}_{user_id}"
|
||||||
|
|
||||||
|
|
||||||
|
def _thread_unique_id(bot_id: int, chat_id: int) -> str:
|
||||||
|
return f"thread_{bot_id}_{chat_id}"
|
||||||
|
|
||||||
|
|
||||||
|
def _last_message_uid(bot_id: int, chat_id: int) -> str:
|
||||||
|
return f"lm_{bot_id}_{chat_id}"
|
||||||
|
|
||||||
|
|
||||||
|
def _antiflood_marker_uid(bot_id: int, chat_id: int) -> str:
|
||||||
|
return f"af_{bot_id}_{chat_id}"
|
||||||
|
|
||||||
|
|
||||||
|
def _on_security_policy(message: types.Message, bot):
|
||||||
|
_ = _get_translator(message)
|
||||||
|
text = _("<b>Политика конфиденциальности</b>\n\n"
|
||||||
|
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При отправке сообщения (кроме команд "
|
||||||
|
"/start и /security_policy) ваш идентификатор пользователя записывается в кеш на некоторое время и потом "
|
||||||
|
"удаляется из кеша. Этот идентификатор используется для общения с оператором.\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>"),
|
||||||
|
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.Unauthorized):
|
||||||
|
await message.reply(_("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"),
|
||||||
|
parse_mode="HTML")
|
||||||
|
return
|
||||||
|
|
||||||
|
bot.outgoing_messages_count = F("outgoing_messages_count") + 1
|
||||||
|
await bot.save(update_fields=["outgoing_messages_count"])
|
||||||
|
|
||||||
|
elif super_chat_id > 0:
|
||||||
|
# в супер-чате кто-то пишет сообщение сам себе, только для личных сообщений
|
||||||
|
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()
|
bot = db_bot_instance.get()
|
||||||
|
|
||||||
if message.text and message.text.startswith("/start"):
|
if message.text and message.text == "/start":
|
||||||
# На команду start нужно ответить, не пересылая сообщение никуда
|
# На команду start нужно ответить, не пересылая сообщение никуда
|
||||||
return SendMessage(chat_id=message.chat.id,
|
text_obj = await BotStartMessage.get_or_none(bot=bot, locale=str(message.from_user.locale))
|
||||||
text=bot.start_text + ServerSettings.append_text())
|
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()
|
super_chat_id = await bot.super_chat_id()
|
||||||
|
|
||||||
if message.chat.id != super_chat_id:
|
if message.chat.id != super_chat_id:
|
||||||
# Это обычный чат: сообщение нужно переслать в супер-чат
|
# Это обычный чат
|
||||||
new_message = await message.forward(super_chat_id)
|
return await handle_user_message(message, super_chat_id, bot)
|
||||||
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id)
|
|
||||||
else:
|
else:
|
||||||
# Это супер-чат
|
# Это супер-чат
|
||||||
if message.reply_to_message:
|
return await handle_operator_message(message, super_chat_id, bot)
|
||||||
# Ответ из супер-чата переслать тому пользователю,
|
|
||||||
chat_id = await _redis.get(_message_unique_id(bot.pk, message.reply_to_message.message_id))
|
|
||||||
if not chat_id:
|
async def edited_message_handler(message: types.Message, *args, **kwargs):
|
||||||
chat_id = message.reply_to_message.forward_from_chat
|
return await message_handler(message, *args, **kwargs, is_edited=True)
|
||||||
if not chat_id:
|
|
||||||
return SendMessage(chat_id=message.chat.id,
|
|
||||||
text="<i>Невозможно переслать сообщение: автор не найден</i>",
|
|
||||||
parse_mode="HTML")
|
|
||||||
chat_id = int(chat_id)
|
|
||||||
try:
|
|
||||||
await message.copy_to(chat_id)
|
|
||||||
except (exceptions.MessageError, exceptions.BotBlocked):
|
|
||||||
await message.reply("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>",
|
|
||||||
parse_mode="HTML")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
await message.forward(super_chat_id)
|
|
||||||
|
|
||||||
|
|
||||||
async def receive_invite(message: types.Message):
|
async def receive_invite(message: types.Message):
|
||||||
|
@ -81,6 +330,18 @@ async def receive_invite(message: types.Message):
|
||||||
break
|
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):
|
async def receive_left(message: types.Message):
|
||||||
bot = db_bot_instance.get()
|
bot = db_bot_instance.get()
|
||||||
if message.left_chat_member.id == message.bot.id:
|
if message.left_chat_member.id == message.bot.id:
|
||||||
|
@ -93,6 +354,23 @@ async def receive_left(message: types.Message):
|
||||||
await bot.save()
|
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):
|
class CustomRequestHandler(WebhookRequestHandler):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -106,19 +384,26 @@ class CustomRequestHandler(WebhookRequestHandler):
|
||||||
if not bot:
|
if not bot:
|
||||||
return None
|
return None
|
||||||
db_bot_instance.set(bot)
|
db_bot_instance.set(bot)
|
||||||
dp = Dispatcher(AioBot(bot.token))
|
dp = Dispatcher(AioBot(bot.decrypted_token()))
|
||||||
|
|
||||||
|
supported_messages = [types.ContentType.TEXT,
|
||||||
|
types.ContentType.CONTACT,
|
||||||
|
types.ContentType.ANIMATION,
|
||||||
|
types.ContentType.AUDIO,
|
||||||
|
types.ContentType.DOCUMENT,
|
||||||
|
types.ContentType.PHOTO,
|
||||||
|
types.ContentType.STICKER,
|
||||||
|
types.ContentType.VIDEO,
|
||||||
|
types.ContentType.VOICE,
|
||||||
|
types.ContentType.LOCATION]
|
||||||
|
dp.register_message_handler(message_handler, content_types=supported_messages)
|
||||||
|
dp.register_edited_message_handler(edited_message_handler, content_types=supported_messages)
|
||||||
|
|
||||||
dp.register_message_handler(message_handler, content_types=[types.ContentType.TEXT,
|
|
||||||
types.ContentType.CONTACT,
|
|
||||||
types.ContentType.ANIMATION,
|
|
||||||
types.ContentType.AUDIO,
|
|
||||||
types.ContentType.DOCUMENT,
|
|
||||||
types.ContentType.PHOTO,
|
|
||||||
types.ContentType.STICKER,
|
|
||||||
types.ContentType.VIDEO,
|
|
||||||
types.ContentType.VOICE])
|
|
||||||
dp.register_message_handler(receive_invite, content_types=[types.ContentType.NEW_CHAT_MEMBERS])
|
dp.register_message_handler(receive_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_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
|
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 import Bot as AioBot
|
||||||
|
from aiogram.types import BotCommand
|
||||||
from olgram.models.models import Bot
|
from olgram.models.models import Bot
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from asyncio import get_event_loop
|
from asyncio import get_event_loop
|
||||||
import ssl
|
import ssl
|
||||||
from olgram.settings import ServerSettings
|
from olgram.settings import ServerSettings
|
||||||
|
from locales.locale import _
|
||||||
from .custom import CustomRequestHandler
|
from .custom import CustomRequestHandler
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -26,14 +28,20 @@ async def register_token(bot: Bot) -> bool:
|
||||||
:param bot: Бот
|
:param bot: Бот
|
||||||
:return: получилось ли
|
: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
|
certificate = None
|
||||||
if ServerSettings.use_custom_cert():
|
if ServerSettings.use_custom_cert():
|
||||||
certificate = open(ServerSettings.public_path(), 'rb')
|
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()
|
await a_bot.session.close()
|
||||||
del a_bot
|
del a_bot
|
||||||
return res
|
return res
|
||||||
|
@ -65,5 +73,5 @@ def main():
|
||||||
runner = web.AppRunner(app)
|
runner = web.AppRunner(app)
|
||||||
loop.run_until_complete(runner.setup())
|
loop.run_until_complete(runner.setup())
|
||||||
logger.info("Server initialization done")
|
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
|
return site
|
||||||
|
|