1
0
mirror of https://github.com/civsocit/olgram.git synced 2025-12-17 13:26:18 +00:00

207 Commits

Author SHA1 Message Date
er8dd
acb62fb644 "interrupt threads" option 2024-03-01 19:39:46 +04:00
jjki3d
ae45374490 flake8 2024-02-17 04:30:05 +04:00
jjki3d
0881c86349 second message option 2024-02-17 03:53:43 +04:00
jjki3d
82b68d1d9f multi-lang fix 2024-01-13 02:29:36 +04:00
jjki3d
a5e6fbce34 multi-lang 2024-01-12 22:35:01 +04:00
walker
28ed36ffeb rm xmr 2023-06-23 12:43:18 +04:00
walker
601c16622d update address 2023-06-17 02:38:37 +04:00
walker
9e46041d0f update year 2023-02-14 23:04:30 +04:00
walker
f41e17a15c rebuild 2023-02-14 22:59:58 +04:00
walker
bb1456dda1 fix location forwarding 2022-11-05 00:42:11 +04:00
walker
756f0bd89a minor fixes 2022-11-05 00:40:26 +04:00
walker
6acc2068de fix for prev 2022-10-29 20:14:50 +04:00
walker
d478e9d8e9 version bump 2022-10-29 19:34:13 +04:00
walker
52864ed729 fix #19 2022-10-29 19:32:56 +04:00
mihalin
ac09e42f94 Merge pull request #24 from arcxio/multiple_admins
support multiple comma-separated values in ADMIN_ID
2022-10-29 19:20:27 +04:00
arĉi
afc5389520 support multiple comma-separated values in ADMIN_ID 2022-10-29 18:52:08 +06:00
mihalin
30ab7c84b4 update translations 2022-09-02 05:12:53 +04:00
mihalin
9d8f5a97f7 Revert "debug print"
This reverts commit 16e944707f.
2022-09-02 04:59:18 +04:00
mihalin
16e944707f debug print 2022-09-02 04:48:16 +04:00
mihalin
9723c70deb leave chat button first iteration 2022-09-02 04:28:51 +04:00
mihalin
6e2ee437ba more nice menu 2022-09-02 04:09:28 +04:00
mihalin
6789d23c28 #ID more useful tag in user info 2022-08-03 00:02:32 +03:00
mihalin
0fd8d541f7 add SUPERVISOR_ID to env example 2022-08-02 23:58:41 +03:00
mihalin
65bc807ab7 version bump 2022-08-01 01:55:45 +03:00
mihalin
f6d47f729d #15 antiflood 2022-08-01 01:53:59 +03:00
mihalin
62d00cbd5f #15 antiflood 2022-08-01 01:52:01 +03:00
mihalin
7bb0951e7f fix for prev 2022-07-23 10:15:05 +03:00
mihalin
3f978c8d1c #16 smart auto-reply 2022-07-23 09:59:37 +03:00
mihalin
c7a52ea9fd typo 2022-07-06 00:50:02 +03:00
mihalin
a4ae50dbbe handle all exceptions on message forwarding 2022-07-06 00:45:51 +03:00
mihalin
d886061981 version bump 2022-07-04 02:50:57 +03:00
mihalin
087891010d fix "two botx in one chat" 2022-07-04 02:46:32 +03:00
mihalin
aa456d3e8d fix 2022-06-30 01:45:44 +03:00
mihalin
4b62762c13 logging print datetime 2022-06-30 01:28:03 +03:00
mihalin
fa2f3f9037 no preview on /start 2022-06-30 01:15:20 +03:00
mihalin
b0d4bc6f27 no removeprefix method 2022-06-30 01:10:18 +03:00
mihalin
55e99becd0 python3.8 2022-06-30 01:09:51 +03:00
mihalin
83db08c93c python3.9 2022-06-26 02:58:19 +03:00
mihalin
03fb55bf12 poetry update 2022-06-26 02:58:05 +03:00
mihalin
f7a4188a53 chat not found handle 2022-06-26 02:51:19 +03:00
mihalin
bfcf8ca414 bump python version 2022-06-25 01:04:38 +03:00
mihalin
8262854acb changelogs 2022-06-25 00:56:33 +03:00
mihalin
948f6af924 changelogs 2022-06-25 00:55:58 +03:00
mihalin
960dd8be5e civsocit link 2022-06-25 00:42:52 +03:00
mihalin
afe3f83d32 fix html second text 2022-06-25 00:26:10 +03:00
mihalin
9f45fb5338 update lang 2022-06-25 00:21:52 +03:00
mihalin
e2e14cfdc1 Revert "try fix migrations"
This reverts commit 1c33d602e0.
2022-06-25 00:14:31 +03:00
mihalin
1c33d602e0 try fix migrations 2022-06-25 00:07:22 +03:00
mihalin
944c5ce002 html support in /start message 2022-06-24 23:58:59 +03:00
mihalin
4063f9f336 don't accept bot own token (for self-hosted projects) 2022-06-24 23:33:01 +03:00
mihalin
b229a2c7e2 promo minor changes 2022-06-24 23:18:23 +03:00
mihalin
74a04c2792 version bump 2022-06-24 23:09:43 +03:00
mihalin
2debd22333 handle more errors 2022-06-24 23:08:32 +03:00
mihalin
09416e94f5 edited message experimental 2022-06-16 04:23:57 +03:00
mihalin
93d65d87c6 Merge branch 'stable' 2022-06-16 04:14:36 +03:00
mihalin
d6b80b8f66 fix additional info messages 2022-06-16 04:10:08 +03:00
mihalin
e3d579fa02 edited message handler experimental 2022-06-16 03:53:19 +03:00
mihalin
27fe37bd6b freeze redis version 2022-06-16 03:42:20 +03:00
mihalin
03437146f1 max bot count for promo 2022-06-16 03:22:15 +03:00
mihalin
c58a4b90d5 flake8 2022-06-16 03:10:09 +03:00
mihalin
3196eed2ac minor fix for prev 2022-06-16 03:07:53 +03:00
mihalin
883879e390 user info minor improvement 2022-06-16 02:59:38 +03:00
mihalin
0d31679280 version inc 2022-05-26 13:53:10 +03:00
mihalin
b2243587a5 увеличить время хранения идентификаторов для уже состоявшихся диалогов 2022-05-26 13:52:47 +03:00
mihalin
e268e5a895 increase redis timeout 2022-05-26 13:16:16 +03:00
mihalin
3725e3fff2 fix notify 2022-05-17 11:34:48 +03:00
mihalin
2891d1cd8b забытые changelogs 2022-05-14 09:57:32 +03:00
mihalin
2909410ce6 minor fixes for prev 2022-05-12 16:17:06 +03:00
mihalin
d5c003400a fix for prev 2022-05-12 16:07:21 +03:00
mihalin
15083fed8d notification, first iteration 2022-05-12 15:59:37 +03:00
mihalin
80f52d0713 Merge branch 'main' into stable 2022-04-12 16:31:54 +03:00
mihalin
dd916da876 забытая надпись в переводе 2022-04-12 16:10:39 +03:00
mihalin
09fc309e38 ignore mo pot 2022-04-12 16:01:28 +03:00
mihalin
483aa4165d english 2022-04-11 18:16:00 +03:00
mihalin
0455c6d022 fix empty locale 2022-04-11 17:24:13 +03:00
mihalin
a7ae47f2a7 flake8 fix 2022-04-11 17:17:35 +03:00
mihalin
cae7822ce3 fix for prev 2022-04-11 16:59:42 +03:00
mihalin
f5407d744d version inc 2022-04-11 15:54:56 +03:00
mihalin
059e97a96d автоматический перевод некоторых сообщений в зависимости от локали устройства 2022-04-11 15:51:00 +03:00
mihalin
b09f8d9cb6 Слава Україні (uk language support) 2022-04-09 07:15:27 +03:00
mihalin
1c4ce35829 возможность отзывать токен 2022-04-09 06:10:48 +03:00
mihalin
e78b0c1150 fix for prev 2022-04-09 05:58:37 +03:00
mihalin
ff28f5cea5 "Этот бот создан с помощью...." возможность выключать в промо 2022-04-09 05:42:52 +03:00
mihalin
7e016a0eb2 Revert "hostname debug"
This reverts commit 042daf90c9.
2022-04-06 22:10:07 +03:00
mihalin
042daf90c9 hostname debug 2022-04-06 22:00:59 +03:00
mihalin
5ce03ca50f малая правка текста 2022-04-02 00:54:27 +03:00
mihalin
654d0047da promo first iteration 2022-03-29 23:17:25 +03:00
mihalin
b9fd2881d9 promo first iteration 2022-03-29 22:36:50 +03:00
mihalin
50ed0ac142 Merge branch 'main' into stable 2022-03-26 21:44:51 +03:00
mihalin
512a892bb9 fix #14 2022-03-26 21:11:17 +03:00
mihalin
83a4f6ae2e no additional text for chinese 2022-03-22 08:22:39 +03:00
mihalin
df2d54156b no additional text for chinese 2022-03-22 08:20:31 +03:00
mihalin
9e3ed843e3 some translation fixes 2022-03-22 07:37:56 +03:00
mihalin
a008d09369 some documentation 2022-03-22 07:25:19 +03:00
mihalin
e209d56ce8 flake8 2022-03-22 06:56:39 +03:00
mihalin
5d5b47ea50 OlgramBot text translation 2022-03-22 06:52:00 +03:00
mihalin
14c85ce634 no technical support for self-hosted chinese bots 2022-03-22 06:31:57 +03:00
mihalin
1a9646d607 Chinese language support (suddenly!) 2022-03-22 05:43:10 +03:00
mihalin
db54473e0f minor fix for prev 2022-03-18 00:21:44 +03:00
mihalin
1c22d2d8d7 minor fix for prev 2022-03-17 23:45:48 +03:00
mihalin
f860fb1815 text fixes 2022-03-17 11:25:06 +03:00
mihalin
9d5bf0de53 fix image 2022-03-17 10:54:54 +03:00
mihalin
9101a81640 fix for prev 2022-03-17 10:22:07 +03:00
mihalin
765676b6e1 забытая картинка 2022-03-17 10:13:30 +03:00
mihalin
fd3645fa52 политика конфиденциальности 2022-03-17 09:44:24 +03:00
mihalin
02e06863e7 documentation 2022-03-17 09:11:15 +03:00
mihalin
8efc40730f рабочий ответ на info сообщение 2022-03-17 08:33:40 +03:00
mihalin
afdb623358 fix for prev, minor refactoring 2022-03-17 08:26:14 +03:00
mihalin
3b26fda9e7 fix for prev 2022-03-17 08:08:50 +03:00
mihalin
1779a5607d #11 additional user info 2022-03-17 08:05:03 +03:00
mihalin
90997f5adb increase redis timeout 2022-03-14 02:53:01 +03:00
mihalin
5ed24b9f42 Revert "debug info in /info command"
This reverts commit 3aa878ff87.
2022-03-13 18:12:22 +03:00
mihalin
569e9f6ccb Revert "debug host print"
This reverts commit cc9479327b.
2022-03-13 18:12:22 +03:00
mihalin
cc9479327b debug host print 2022-03-13 17:04:15 +03:00
mihalin
3aa878ff87 debug info in /info command 2022-03-13 16:45:31 +03:00
mihalin
ce408591c4 fix for prev 2022-03-13 02:29:34 +03:00
mihalin
2e03be7829 pass command-line arguments docker 2022-03-13 02:26:22 +03:00
mihalin
10e140814d some debug info 2022-03-13 02:19:33 +03:00
mihalin
59408aaacd readme 2022-03-08 04:21:38 +03:00
mihalin
73bcdcc3c3 fix migration 2022-02-20 10:55:11 +03:00
mihalin
31d2acc7fa doc 2022-02-19 20:45:53 +03:00
mihalin
715d516952 enable and disable threads 2022-02-19 20:40:56 +03:00
mihalin
64ba75e8cb version bump 2022-02-19 05:54:29 +03:00
mihalin
fc607cee5c запуск без olgram и без сервера (переход на два контейнера) 2022-02-19 05:53:53 +03:00
mihalin
6004f9d9af не увеличивать incoming messages при возможных ошибках на бекенде 2022-02-19 05:38:03 +03:00
mihalin
994d96885f about threads 2022-02-19 03:22:07 +03:00
mihalin
773c55f8c0 about threads 2022-02-19 03:20:01 +03:00
mihalin
35148883db Merge branch 'main' into stable 2022-02-19 02:43:50 +03:00
mihalin
88752a01dd reply exception skip 2022-02-19 02:41:59 +03:00
mihalin
bb49f6a702 some changelogs 2022-02-19 02:29:35 +03:00
mihalin
c5c7468e36 Merge branch 'main' into stable 2022-02-19 00:59:03 +03:00
mihalin
2bba944dc0 симметрия в тексте 2022-02-19 00:58:31 +03:00
mihalin
23dacbfe8f Merge branch 'main' into stable 2022-02-19 00:56:10 +03:00
mihalin
4be45985a0 minor edition 2022-02-18 22:09:37 +03:00
mihalin
1768d9e7ea Немного статистики 2022-02-18 21:50:23 +03:00
mihalin
6f602f417f Немного статистики 2022-02-18 21:47:40 +03:00
mihalin
5cff8da9cd webhook less connections 2022-02-18 07:51:51 +03:00
mihalin
36a0bc0f95 ещё немного статистики 2022-02-17 02:56:11 +03:00
mihalin
878abc6a0f threads first iteration 2022-02-16 20:52:08 +03:00
mihalin
d4582d9a9d threads first iteration 2022-02-16 19:56:03 +03:00
mihalin
02df39c9fd redis timeout 2022-02-16 18:45:44 +03:00
mihalin
a504d38418 handle deactivated error 2022-02-12 03:40:28 +03:00
mihalin
4c22563974 Merge branch 'main' into stable 2022-02-12 03:39:29 +03:00
mihalin
24710d6b5f backup ignore 2022-02-12 03:37:55 +03:00
mihalin
2164ee6f2c templates minor improvements 2022-02-12 01:28:10 +03:00
mihalin
a3eb985d28 minor fixes 2022-02-11 21:19:30 +03:00
mihalin
d53a574377 add images 2022-02-11 16:27:21 +03:00
mihalin
fbd546e59a inline doc first step 2022-02-11 16:15:50 +03:00
mihalin
bd31e21699 no spam in hroup chat 2022-02-11 04:42:19 +03:00
mihalin
767cfe64ee inline cache 2022-02-11 04:38:07 +03:00
mihalin
7c3069ccb8 fix for prev 2022-02-11 04:26:09 +03:00
mihalin
a5a6d5beac inline permissions 2022-02-11 04:24:11 +03:00
mihalin
0fbfa9bd1e flake8 2022-02-11 04:08:14 +03:00
mihalin
9e21b15781 fix phrases 2022-02-11 04:06:28 +03:00
mihalin
bd239f6b2f print debug 2022-02-11 03:59:58 +03:00
mihalin
6b3383418e logging 2022-02-11 02:09:09 +03:00
mihalin
a7a08639cf inlines first iteration 2022-02-11 02:02:28 +03:00
mihalin
177603606f flake8 2022-02-11 01:04:15 +03:00
mihalin
96853f4e09 version inc 2022-02-11 01:02:43 +03:00
mihalin
45e28bf9b7 templates first iteration 2022-02-11 01:02:23 +03:00
mihalin
ea5249d1b8 flake8 2022-01-27 04:12:14 +03:00
mihalin
bea77807af Merge pull request #9 from BelarusRazam/main
Some improvements to development process. Improve Docker compatibility.
2022-01-27 04:05:46 +03:00
GordonFreeman-BY
880269d9d8 Add possibility to set loglevel from environment. 2022-01-24 03:43:16 +03:00
GordonFreeman-BY
d8b580d81b Remove host varible from web server. 2022-01-24 03:35:24 +03:00
mihalin
8bdd8307d5 handle deactivated error 2022-01-23 00:25:49 +03:00
mihalin
01bcbbb052 fix directory creation 2022-01-22 20:31:36 +03:00
mihalin
47cd78a349 $$ 2022-01-19 22:24:48 +03:00
mihalin
fd8c87fb78 doc fix 2022-01-19 21:22:54 +03:00
mihalin
151a4d9cb7 doc 2022-01-19 16:29:35 +03:00
mihalin
cf937f8dc2 fix for prev 2022-01-19 16:15:03 +03:00
mihalin
450e283e50 fix for prev 2022-01-19 16:12:43 +03:00
mihalin
164a251310 fix for prev 2022-01-19 16:09:29 +03:00
mihalin
8d5723e062 no more requirements.txt 2022-01-19 16:05:10 +03:00
mihalin
bc5186ba26 ban unban 2022-01-19 15:56:39 +03:00
mihalin
d0f9042fb6 logging 2022-01-19 15:48:50 +03:00
mihalin
173014fda0 loop 2022-01-19 00:42:29 +03:00
mihalin
9f0c03fb68 version bump 2022-01-19 00:10:37 +03:00
mihalin
603ae506f2 banned migration 2022-01-19 00:09:23 +03:00
mihalin
68502b7756 fix aerich version 2022-01-18 23:42:42 +03:00
mihalin
363391b575 ban\unban commands 2022-01-18 23:28:03 +03:00
mihalin
59b73c33dc add poetry 2022-01-18 03:30:39 +03:00
mihalin
067fbc2736 use poetry 2022-01-18 03:21:28 +03:00
mihalin
4aced9af91 оказывается, мы не поддерживаем heroku 2022-01-14 23:27:41 +03:00
mihalin
facdbbc2fe fox for prev 2022-01-14 20:47:53 +03:00
mihalin
9ce03e048e правки по инструкции после обращения в тех. поддержку: heroku, кавычки в .env 2022-01-12 00:40:39 +03:00
mihalin
47d9f510a9 custom certificate create directory 2022-01-12 00:17:33 +03:00
mihalin
46ba3a57aa fix info 2021-12-24 19:56:01 +03:00
mihalin
971fd178f2 info 2021-12-24 19:46:46 +03:00
mihalin
d83ff39067 enable info command 2021-12-24 19:39:25 +03:00
mihalin
2786af259a info 2021-12-24 19:20:53 +03:00
mihalin
1a1d382243 last changes back 2021-12-24 19:06:48 +03:00
mihalin
74ae7c5d14 debug 2021-12-23 21:35:53 +03:00
mihalin
2c6ef7bed9 search bug ... 2021-12-23 01:00:16 +03:00
mihalin
9d6fccd204 search bug ... 2021-12-23 00:06:38 +03:00
mihalin
c686d8d2d6 fix #8 2021-12-22 23:46:09 +03:00
mihalin
fcba54ccf5 typo 2021-12-22 22:24:41 +03:00
mihalin
b38834b265 При создании группового чата он добавляется в список olgram 2021-12-15 00:14:49 +03:00
mihalin
dcee7a98df example.env мелкие правки 2021-12-14 23:57:43 +03:00
mihalin
645357995b миграция ID чата (fix #7) 2021-12-14 23:55:19 +03:00
mihalin
ba0c2752a1 более понятный текст кнопки автоответчика 2021-12-09 23:05:56 +03:00
mihalin
b2cc2a4827 Merge remote-tracking branch 'origin/main' 2021-10-23 20:34:30 +03:00
mihalin
5a2e950839 Мелкие правки по инструкции 2021-10-23 20:33:33 +03:00
mihalin
6c98c988ca Update README.md 2021-10-16 02:50:48 +03:00
mihalin
c6266cfdf2 Update README.md 2021-10-16 02:50:35 +03:00
mihalin
de68f0d002 Merge pull request #5 from milksense/patch-1
Update developer.rst
2021-10-11 18:39:20 +03:00
ᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠᅠ
bef0e183b4 Update developer.rst 2021-10-11 16:33:52 +01:00
mihalin
04c7711b74 fix для ограничения прав 2021-10-02 14:47:33 +03:00
65 changed files with 4772 additions and 268 deletions

View File

@@ -1,3 +1,3 @@
[flake8]
exclude = .git,__pycache__,venv
exclude = .git,__pycache__,venv,.venv
max-line-length = 120

View File

@@ -4,6 +4,8 @@ on: push
env:
PYTHONUNBUFFERED: 1
POETRY_VERSION: 1.1.2
POETRY_VIRTUALENVS_CREATE: "false"
jobs:
lint:
@@ -17,7 +19,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install flake8
pip install poetry
poetry install
- name: Check flake8
run: python -m flake8 .
run: flake8 .

6
.gitignore vendored
View File

@@ -7,3 +7,9 @@ __pycache__
config.json
docker-compose-release.yaml
docs/build
ad.md
release.env
test.py
backup
*.mo
*.pot

25
.readthedocs.yaml Normal file
View 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

View File

@@ -1,11 +1,26 @@
FROM python:3.8-buster
COPY . /app
ENV PYTHONUNBUFFERED=1 \
POETRY_VERSION=1.1.12 \
POETRY_VIRTUALENVS_CREATE="false"
RUN apt-get update && \
apt-get install -y gettext build-essential && \
apt-get clean && rm -rf /var/cache/apt/* && rm -rf /var/lib/apt/lists/* && rm -rf /tmp/*
RUN pip install "poetry==$POETRY_VERSION"
WORKDIR /app
RUN pip install --upgrade pip && \
pip install -r requirements.txt
COPY pyproject.toml poetry.lock docker-entrypoint.sh ./
RUN poetry install --no-interaction --no-ansi --no-dev
COPY . /app
RUN msgfmt locales/zh/LC_MESSAGES/olgram.po -o locales/zh/LC_MESSAGES/olgram.mo --use-fuzzy
RUN msgfmt locales/uk/LC_MESSAGES/olgram.po -o locales/uk/LC_MESSAGES/olgram.mo --use-fuzzy
RUN msgfmt locales/en/LC_MESSAGES/olgram.po -o locales/en/LC_MESSAGES/olgram.mo --use-fuzzy
EXPOSE 80

View File

@@ -4,43 +4,23 @@
[![Deploy Status](https://github.com/civsocit/olgram/workflows/Deploy/badge.svg)](https://github.com/civsocit/olgram/actions?workflow=Deploy)
[![Documentation](https://readthedocs.org/projects/olgram/badge/?version=latest)](https://olgram.readthedocs.io)
[@olgram](https://t.me/olgrambot) - конструктор ботов обратной связи в Telegram
[@OlgramBot](https://t.me/olgrambot) - конструктор ботов обратной связи в Telegram
Документация: https://olgram.readthedocs.io
## Возможности и преимущества Olgram Bot
* **Общение с клиентами**. После подключения бота, вы сможете общаться с вашими пользователями бота через диалог с
ботом, либо подключенный отдельно чат, где может находиться ваш колл-центр.
* **Все типы сообщений**. Livegram боты поддерживают все типы сообщений — текст, фото, видео, голосовые сообщения и
стикеры.
* **Open-source**. В отличие от известного проекта Livegram код нашего конструктора полностью открыт.
* **Self-hosted**. Вы можете развернуть свой собственный конструктор, если не доверяете нашему.
* **Безопасность**. В отличие от Livegram, мы не храним сообщения, которые вы отправляете в бот. А наши сервера
располагаются в Германии, что делает проект неподконтрольным российским властям.
**Olgram** [@OlgramBot](https://t.me/olgrambot) это конструктор, который позволяет создавать боты обратной связи
в Telegram. После подключения к Olgram пользователи вашего бота смогут писать сообщения, которые будут
пересылаться вам в чат, где вы сможете на них ответить.
Такие боты могут вам пригодиться, например:
По любым вопросам, связанным с Olgram, пишите в наш бот обратной связи
[@civsocit_feedback_bot](https://t.me/civsocit_feedback_bot)
*Пример 1.* Вы администрируете Telegram-канал и хотите дать своим подписчикам возможность связаться с вами,
но не хотите оставлять свои личные контакты. Тогда вы можете создать бота обратной связи: подписчики будут писать
боту, вы будете отвечать через бота анонимно.
### Для разработчиков: сборка и запуск проекта
*Пример 2.* Вы организуете небольшой call-центр в Telegram или группу технической поддержки. С помощью бота обратной
связи вы можете принимать заявки от пользователей в общий чат ваших специалистов, обсуждать эти заявки и отвечать
пользователям прямо из этого чата.
Вам потребуется собственный VPS или любой хост со статическим адресом или доменом.
* Создайте файл .env по образцу example.env. Вам нужно заполнить переменные:
* BOT_TOKEN - токен нового бота, получить у [@botfather](https://t.me/botfather)
* POSTGRES_PASSWORD - любой случайный пароль
* TOKEN_ENCRYPTION_KEY - любой случайный пароль, отличный от POSTGRES_PASSWORD
* WEBHOOK_HOST - IP адрес или доменное имя сервера, на котором запускается проект
* Сохраните файл docker-compose.yaml и соберите его:
```
sudo docker-compose up -d
```
В docker-compose.yaml минимальная конфигурация. Для использования в серьёзных проектах мы советуем:
* Приобрести домен и настроить его на свой хост
* Наладить реверс-прокси и автоматическое обновление сертификатов - например, с помощью
[Traefik](https://github.com/traefik/traefik)
* Скрыть IP сервера с помощью [Cloudflire](https://www.cloudflare.com), чтобы пользователи ботов не могли найти IP адрес
хоста по Webhook бота.
Пример более сложной конфигурации есть в файле docker-compose-full.yaml
Читайте больше: https://olgram.readthedocs.io

View File

@@ -1,7 +1,9 @@
# Конфигурация, удобная для разработки в PyCharm: бот запускается без docker, порты postgres и redis открыты на localhost
# Не используйте её в production!
version: '3'
services:
postgres:
image: kartoza/postgis
image: postgres:14
environment:
- POSTGRES_USER=test_user
- POSTGRES_PASSWORD=test_passwd
@@ -11,7 +13,7 @@ services:
volumes:
- database:/var/lib/postgresql/data
redis:
image: 'bitnami/redis:latest'
image: 'bitnami/redis:6.2.7'
environment:
- ALLOW_EMPTY_PASSWORD=yes
volumes:

View File

@@ -1,3 +1,4 @@
# Пример сложной конфигурации сервера: реверс-прокси, автоматическое обновление github
version: '3'
services:
postgres:
@@ -12,7 +13,7 @@ services:
labels:
- 'com.centurylinklabs.watchtower.enable="false"'
redis:
image: 'bitnami/redis:latest'
image: 'bitnami/redis:6.2.7'
restart: unless-stopped
environment:
- ALLOW_EMPTY_PASSWORD=yes

36
docker-compose-src.yaml Normal file
View 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:

View File

@@ -1,3 +1,4 @@
# Минимальная конфигурация сервера
version: '3'
services:
postgres:
@@ -8,7 +9,7 @@ services:
volumes:
- database:/var/lib/postgresql/data
redis:
image: 'bitnami/redis:latest'
image: 'bitnami/redis:6.2.7'
restart: unless-stopped
environment:
- ALLOW_EMPTY_PASSWORD=yes

View File

@@ -5,11 +5,11 @@ if [ ! -z "${CUSTOM_CERT}" ]; then
echo "Use custom certificate"
if [ ! -f /cert/private.key ]; then
echo "Generate new certificate"
openssl req -newkey rsa:2048 -sha256 -nodes -keyout /cert/private.key -x509 -days 1000 -out /cert/public.pem -subj "/C=US/ST=Berlin/L=Berlin/O=my_org/CN=${WEBHOOK_HOST}"
openssl req -newkey rsa:2048 -sha256 -nodes -keyout /cert/private.key -x509 -days 10000 -out /cert/public.pem -subj "/C=US/ST=Berlin/L=Berlin/O=my_org/CN=${WEBHOOK_HOST}"
fi
fi
sleep 10
aerich upgrade
python migrate.py
python main.py
python main.py $@

BIN
docs/images/ban1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

BIN
docs/images/ban2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

BIN
docs/images/inline.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
docs/images/thread.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
docs/images/user_info.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -1,6 +1,7 @@
О проекте
===================================
Зачем нужен Olgram
------------

View File

@@ -0,0 +1,47 @@
Дополнительно
=============
Донаты
----------------
На аренду сервера для этого проекта
Bitcoin:
``bc1qlq7cm5chc8flr3fy8ewk967aknq3dwmxtwn9hl``
Litecoin:
``LTC1QXAJSVZ0LW44AA5NYTUCH8CP2G8X7A4CDASE4Y7``
Как убрать "Этот бот создан с помощью ..."
----------------
Напишите нам на `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
История изменений
----------------
- `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

View File

@@ -3,7 +3,7 @@
# -- Project information
project = 'Olgram'
copyright = '2021, Civsocit'
copyright = '2024, Civsocit'
author = 'civsocit'
release = '0.1'

View File

@@ -1,6 +1,7 @@
Для разработчиков
=================
.. _run:
Сборка и запуск
---------------
Вы можете развернуть Olgram на своём сервере. Вам потребуется собственный VPS или любой хост со статическим адресом
@@ -28,6 +29,24 @@
Не потеряйте 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
Дополнительно
-------------
@@ -35,7 +54,7 @@
* Приобрести домен и настроить его на свой хост
* Наладить реверс-прокси и автоматическое обновление сертификатов - например, с помощью `Traefik <https://github.com/traefik/traefik>`_
* Скрыть IP сервера с помощью `Cloudflire <https://www.cloudflare.com>`_, чтобы пользователи ботов не могли найти IP адрес хоста по Webhook бота.
* Скрыть IP сервера с помощью `Cloudflare <https://www.cloudflare.com>`_, чтобы пользователи ботов не могли найти IP адрес хоста по Webhook бота.
Пример более сложной конфигурации есть в файле `docker-compose-full.yaml <https://github.com/civsocit/olgram/blob/main/docker-compose-full.yaml>`_
@@ -50,3 +69,12 @@
Идентификатор чата это либо ваш Telegram ID, либо ID группового чата Telegram. Идентификатор можно посмотреть
командой /chatid.
Настройка языка
---------------
Язык по-умолчанию - русский. Поддержку другого языка можно добавлять по образцу китайского в папку locales/
(китайский - zh). Код языка указать в настройках .env
``O_LANG=<идентификатор языка>``

View File

@@ -14,7 +14,10 @@
about
quick_start
templates
options
developer
additional
.. note::

69
docs/source/options.rst Normal file
View File

@@ -0,0 +1,69 @@
Опции
=============
.. _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 часа. Без этой опции поток сообщений не
прерывается никогда.

View File

@@ -4,7 +4,7 @@
Как создать бота
----------------
Перейдите по ссылке `@Olgram <https://t.me/olgrambot>`_ и нажмите Запустить:
Перейдите по ссылке `@OlgramBot <https://t.me/olgrambot>`_ и нажмите Запустить:
.. image:: ../images/start.jpg
@@ -68,6 +68,13 @@ BotFather - это официальный бот Telegram, создающий д
Теперь просто отправьте новый текст приветствия.
.. note::
Чтобы настроить особый текст приветствия для, например, русскоязычных пользователей (т.е. тех пользователей, у
которых в настройках Telegram выставлена русская локализация), нажмите кнопку "Руссикй 🇷🇺" и только потом отправьте
текст приветствия. Чтобы отредактировать текст приветствия для всех остальных языков, нажмите "[все языки]".
Как привязать бота к групповому чату
------------------------------------
@@ -90,6 +97,20 @@ BotFather - это официальный бот Telegram, создающий д
Нужно сначала зарегистрировать своего бота в Olgram, и только потом добавить в групповой чат. Если бот уже
добавлен в групповой чат, удалите его оттуда и добавьте заново - тогда Olgram сможет пересылать туда сообщения.
Как блокировать и разблокировать пользователей
------------------------------------
Вы можете отправлять в бан пользователей feedback бота. Для этого есть команды /ban и /unban.
Например так:
.. image:: ../images/ban2.png
:width: 300
Со стороны пользователя этот диалог будет выглядеть так:
.. image:: ../images/ban1.png
:width: 300
.. note::
Если у вас возникли вопросы по использованию бота, или вы нашли ошибку - напишите

32
docs/source/templates.rst Normal file
View 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 бота

View File

@@ -1,16 +1,35 @@
BOT_TOKEN=YOUR_BOT_TOKEN_HERE # example: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12
# example: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12 (without quotes!)
BOT_TOKEN=YOUR_BOT_TOKEN_HERE
POSTGRES_USER=olgram
POSTGRES_PASSWORD=SOME_RANDOM_PASSWORD_HERE # example: x2y0n27ihiez93kmzj82
# example: x2y0n27ihiez93kmzj82 (without quotes!)
POSTGRES_PASSWORD=SOME_RANDOM_PASSWORD_HERE
POSTGRES_DB=olgram
POSTGRES_HOST=postgres
TOKEN_ENCRYPTION_KEY=SOME_RANDOM_PASSWORD_HERE # example: i7flci0mx4z5patxnl6m
# example: i7flci0mx4z5patxnl6m (without quotes!)
TOKEN_ENCRYPTION_KEY=SOME_RANDOM_PASSWORD_HERE
# ADMIN_ID=223453418 # use your user id or group chat id to restrict access to the bot
# use your user id or group chat id to restrict access to the bot
# ADMIN_ID=223453418
WEBHOOK_HOST=YOUR_HOST_HERE # example: 11.143.142.140 or my_domain.com
WEBHOOK_PORT=8443 # allowed: 80, 443, 8080, 8443
CUSTOM_CERT=true # use that if you don't set up your own domain and let's encrypt certificate
# use your user id or group chat id to give selected users access to the bot's general statistics (/info command)
# SUPERVISOR_ID=223453419
# example: 11.143.142.140 or my_domain.com (without quotes, without 'https://' prefix!)
WEBHOOK_HOST=YOUR_HOST_HERE
# allowed: 80, 443, 8080, 8443
WEBHOOK_PORT=8443
# use that if you don't set up your own domain and let's encrypt certificate
CUSTOM_CERT=true
REDIS_PATH=redis://redis
# Set log level, can be CRITICAL, ERROR, WARNING, INFO, DEBUG. By default it set to WARNING.
LOGLEVEL=
# Uncomment this to switch bot language to English
# O_LANG=en

View File

@@ -0,0 +1,721 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2022-09-02 05:02+0400\n"
"PO-Revision-Date: 2022-09-02 05:07+0400\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.1\n"
#: olgram/commands/admin.py:21 olgram/commands/info.py:21
#: olgram/commands/promo.py:23 olgram/commands/promo.py:39
msgid "Недостаточно прав"
msgstr "Not enough permissions"
#: olgram/commands/admin.py:27
msgid "Нужно указать имя бота"
msgstr "You need to specify the bot's name"
#: olgram/commands/admin.py:33
msgid "Такого бота нет в системе"
msgstr "There is no such bot"
#: olgram/commands/admin.py:39 olgram/commands/admin.py:53
msgid "Пропустить"
msgstr "Skip"
#: olgram/commands/admin.py:42
msgid ""
"Введите текст, который будет отправлен владельцу бота {0}. Напишите "
"'Пропустить' чтобы отменить"
msgstr ""
"Enter the text that will be sent to the owner of the bot {0}. Write 'Skip' "
"to cancel"
#: olgram/commands/admin.py:50
msgid "Поддерживается только текст"
msgstr "Only text is supported"
#: olgram/commands/admin.py:55 olgram/commands/admin.py:71
msgid "Отменено"
msgstr "Cancelled"
#: olgram/commands/admin.py:61 olgram/commands/admin.py:69
msgid "Отправить"
msgstr "Send"
#: olgram/commands/admin.py:62
msgid "Отменить"
msgstr "Cancel"
#: olgram/commands/admin.py:81
msgid "Отправлено"
msgstr "Sent"
#: olgram/commands/bot_actions.py:22
msgid "Бот удалён"
msgstr "Bot removed"
#: olgram/commands/bot_actions.py:38 olgram/commands/bot_actions.py:50
msgid "Текст сброшен"
msgstr "Text is reset"
#: olgram/commands/bot_actions.py:64
msgid "Выбран личный чат"
msgstr "Personal chat selected"
#: olgram/commands/bot_actions.py:77
msgid "Бот вышел из чатов"
msgstr "Bot leaved chats"
#: olgram/commands/bot_actions.py:83
msgid "Нельзя привязать бота к этому чату"
msgstr "You can't bind a bot to this chat room"
#: olgram/commands/bot_actions.py:87
msgid "Выбран чат {0}"
msgstr "Selected chat {0}"
#: olgram/commands/bots.py:46
msgid ""
"У вас уже слишком много ботов. Удалите какой-нибудь свой бот из Olgram(/"
"mybots -> (Выбрать бота) -> Удалить бот)"
msgstr ""
"You already have too many bots. Remove any of your bots from Olgram(/mybots -"
"> (Select bot) -> Remove bot)"
#: olgram/commands/bots.py:50
msgid ""
"\n"
" Чтобы подключить бот, вам нужно выполнить три действия:\n"
"\n"
" 1. Перейдите в бот @BotFather, нажмите START и отправьте команду /"
"newbot\n"
" 2. Введите название бота, а потом username бота.\n"
" 3. После создания бота перешлите ответное сообщение в этот бот или "
"скопируйте и пришлите token бота.\n"
"\n"
" Важно: не подключайте боты, которые используются в других сервисах "
"(Manybot, Chatfuel, Livegram и других).\n"
" "
msgstr ""
"\n"
" To connect the bot, you need to follow three steps:\n"
"\n"
" 1. Go to bot @BotFather, press START and send command /newbot\n"
" 2. Enter the bot's name and then the bot's username.\n"
" 3. Once the bot is created, forward a reply message to this bot or copy "
"and send the bot's token.\n"
"\n"
" Important: do not connect bots that are used in other services (Manybot, "
"Chatfuel, Livegram and others).\n"
" "
#: olgram/commands/bots.py:70
msgid ""
"\n"
" Это не токен бота.\n"
"\n"
" Токен выглядит вот так: 123456789:AAAA-"
"abc123_AbcdEFghijKLMnopqrstu12\n"
" "
msgstr ""
"\n"
" This is not a bot token.\n"
"\n"
" The token looks like this: 123456789:AAAA-"
"abc123_AbcdEFghijKLMnopqrstu12\n"
" "
#: olgram/commands/bots.py:77
msgid ""
"\n"
" Не удалось запустить этого бота: неверный токен\n"
" "
msgstr ""
"\n"
" Failed to start this bot: Wrong token\n"
" "
#: olgram/commands/bots.py:82
msgid ""
"\n"
" Не удалось запустить этого бота: непредвиденная ошибка\n"
" "
msgstr ""
"\n"
" Failed to start this bot: unexpected error\n"
" "
#: olgram/commands/bots.py:87
msgid ""
"\n"
" Такой бот уже есть в базе данных\n"
" "
msgstr ""
"\n"
" Such a bot is already in the database\n"
" "
#: olgram/commands/bots.py:122
msgid "Бот добавлен! Список ваших ботов: /mybots"
msgstr "Bot added! List of your bots: /mybots"
#: olgram/commands/info.py:34
msgid "Количество ботов: {0}\n"
msgstr "Number of bots: {0}\n"
#: olgram/commands/info.py:35
msgid "Количество пользователей (у конструктора): {0}\n"
msgstr "Number of users (at the constructor): {0}\n"
#: olgram/commands/info.py:36
msgid "Шаблонов ответов: {0}\n"
msgstr "Answer templates: {0}\n"
#: olgram/commands/info.py:37
msgid "Входящих сообщений у всех ботов: {0}\n"
msgstr "Incoming messages from all bots: {0}\n"
#: olgram/commands/info.py:38
msgid "Исходящих сообщений у всех ботов: {0}\n"
msgstr "All bots have outgoing messages: {0}\n"
#: olgram/commands/info.py:39
msgid "Промо-кодов выдано: {0}\n"
msgstr "Promo codes issued: {0}\n"
#: olgram/commands/menu.py:31
msgid ""
"\n"
" У вас нет добавленных ботов.\n"
"\n"
" Отправьте команду /addbot, чтобы добавить бот.\n"
" "
msgstr ""
"\n"
" You do not have any bots added.\n"
"\n"
" Send the command /addbot to add a bot.\n"
" "
#: olgram/commands/menu.py:46
msgid "Ваши боты"
msgstr "Your bots"
#: olgram/commands/menu.py:67
msgid "Личные сообщения"
msgstr "Personal messages"
#: olgram/commands/menu.py:72
msgid "❗️ Выйти из всех чатов"
msgstr "❗️ Leave all chats"
#: olgram/commands/menu.py:77 olgram/commands/menu.py:122
#: olgram/commands/menu.py:148 olgram/commands/menu.py:184
#: olgram/commands/menu.py:247
msgid "<< Назад"
msgstr "<< Back"
#: olgram/commands/menu.py:83
msgid ""
"\n"
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
"вам в бот.\n"
" Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню "
"ещё раз и выберите добавленный чат.\n"
" Если ваш бот состоял в групповом чате до того, как его добавили в "
"Olgram - удалите бота из чата и добавьте\n"
" снова.\n"
" "
msgstr ""
"\n"
" This bot is not added to the chats, so all messages will come to you "
"in the bot.\n"
" To add a chat - add the bot @{0} to the chat, open this menu again "
"and select the added chat.\n"
" If your bot was in a group chat before you added it to Olgram - "
"remove the bot from the chat and add\n"
" again.\n"
" "
#: olgram/commands/menu.py:90
msgid ""
"\n"
" В этом разделе вы можете привязать бота @{0} к чату.\n"
" Выберите чат, куда бот будет пересылать сообщения.\n"
" "
msgstr ""
"\n"
" In this section you can bind the @{0} bot to a chat room.\n"
" Select the chat room where the bot will forward messages.\n"
" "
#: olgram/commands/menu.py:102
msgid "Текст"
msgstr "Text"
#: olgram/commands/menu.py:107
msgid "Чат"
msgstr "Chat"
#: olgram/commands/menu.py:112
msgid "Удалить бот"
msgstr "Delete bot"
#: olgram/commands/menu.py:117
msgid "Статистика"
msgstr "Statistics"
#: olgram/commands/menu.py:126
msgid "Опции"
msgstr "Options"
#: olgram/commands/menu.py:131
msgid ""
"\n"
" Управление ботом @{0}.\n"
"\n"
" Если у вас возникли вопросы по настройке бота, то посмотрите нашу "
"справку /help или напишите нам\n"
" @civsocit_feedback_bot\n"
" "
msgstr ""
"\n"
" Bot management @{0}.\n"
"\n"
" If you have any questions about configuring the bot, see our help /help "
"or email us\n"
" @civsocit_feedback_bot\n"
" "
#: olgram/commands/menu.py:143
msgid "Да, удалить бот"
msgstr "Yes, delete the bot"
#: olgram/commands/menu.py:152
msgid ""
"\n"
" Вы уверены, что хотите удалить бота @{0}?\n"
" "
msgstr ""
"\n"
" Are you sure you want to delete the bot @{0}?\n"
" "
#: olgram/commands/menu.py:161
msgid "Потоки сообщений"
msgstr "Message threads"
#: olgram/commands/menu.py:166
msgid "Данные пользователя"
msgstr "User data"
#: olgram/commands/menu.py:171
msgid "Антифлуд"
msgstr "Antiflood"
#: olgram/commands/menu.py:178
msgid "Olgram подпись"
msgstr "Olgram signature"
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
msgid "включены"
msgstr "enabled"
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
msgid "выключены"
msgstr "disabled"
#: olgram/commands/menu.py:191
#, fuzzy
#| msgid "включены"
msgid "включен"
msgstr "enabled"
#: olgram/commands/menu.py:191
#, fuzzy
#| msgid "выключены"
msgid "выключен"
msgstr "disabled"
#: olgram/commands/menu.py:192
msgid ""
"\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#threads\">Потоки сообщений</a>: <b>{0}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
"info\">Данные пользователя</a>: <b>{1}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#antiflood\">Антифлуд</a>: <b>{2}</b>\n"
" "
msgstr ""
"\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#threads\">Threads</a>: <b>{0}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
"info\">User data</a>: <b>{1}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#antiflood\">Antiflood</a>: <b>{2}</b>"
#: olgram/commands/menu.py:199
msgid "включена"
msgstr "enabled"
#: olgram/commands/menu.py:199
msgid "выключена"
msgstr "disabled"
#: olgram/commands/menu.py:200
msgid "Olgram подпись: <b>{0}</b>"
msgstr "Olgram signature: <b>{0}</b>"
#: olgram/commands/menu.py:210 olgram/commands/menu.py:272
#: olgram/commands/menu.py:314
msgid "<< Завершить редактирование"
msgstr "<< Finish editing"
#: olgram/commands/menu.py:214
msgid "Автоответчик"
msgstr "Autoresponder"
#: olgram/commands/menu.py:219 olgram/commands/menu.py:286
msgid "Сбросить текст"
msgstr "Reset text"
#: olgram/commands/menu.py:224
msgid ""
"\n"
" Сейчас вы редактируете текст, который отправляется после того, как "
"пользователь отправит вашему боту @{0}\n"
" команду /start\n"
"\n"
" Текущий текст:\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n"
" "
msgstr ""
"\n"
" You are now editing the text that is sent after the user sends your bot "
"@{0}\n"
" /start command.\n"
"\n"
" Current text:\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Send a message to change the text.\n"
" "
#: olgram/commands/menu.py:251
msgid ""
"\n"
" Статистика по боту @{0}\n"
"\n"
" Входящих сообщений: <b>{1}</b>\n"
" Ответных сообщений: <b>{2}</b>\n"
" Шаблоны ответов: <b>{3}</b>\n"
" Забанено пользователей: <b>{4}</b>\n"
" "
msgstr ""
"\n"
" Statistics @{0}\n"
"\n"
" Income messages: <b>{1}</b>\n"
" Response messages: <b>{2}</b>\n"
" Tempaltes: <b>{3}</b>\n"
" Banned users: <b>{4}</b>\n"
" "
#: olgram/commands/menu.py:276
msgid "Предыдущий текст"
msgstr "Previous text"
#: olgram/commands/menu.py:281
msgid "Шаблоны ответов..."
msgstr "Answer templates..."
#: olgram/commands/menu.py:291
msgid ""
"\n"
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
"отключено.\n"
"\n"
" Текущий текст:\n"
" <pre>"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n"
" "
msgstr ""
"\n"
" You are now editing the autoresponder text. This message is sent in "
"response to all incoming @{0} messages automatically. It is disabled by "
"default.\n"
"\n"
" Current text:\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Send a message to change the text.\n"
" "
#: olgram/commands/menu.py:301
msgid "(отключено)"
msgstr "(disabled)"
#: olgram/commands/menu.py:318
msgid ""
"\n"
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
"\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте какую-нибудь фразу (например: \"Ваш заказ готов, ожидайте!\"), "
"чтобы добавить её в шаблон.\n"
" Чтобы удалить шаблон из списка, отправьте его номер в списке (например, "
"4)\n"
" "
msgstr ""
"\n"
" You are currently editing the answer templates for @{0}. Current "
"templates:\n"
"\n"
" <pre>\n"
" {1}\n"
" </pre>.\n"
" Send some phrase (e.g., \"Your order is ready, wait!\") to add to the "
"template.\n"
" To remove a template from the list, send its number in the list (for "
"example, 4) "
#: olgram/commands/menu.py:337
msgid "(нет шаблонов)"
msgstr "(no templates)"
#: olgram/commands/menu.py:376
msgid "У вас нет шаблонов, чтобы их удалять"
msgstr "You don't have templates to delete them"
#: olgram/commands/menu.py:378
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
msgstr "To delete a template, enter a number between 0 and {0}"
#: olgram/commands/menu.py:386
msgid "У вашего бота уже слишком много шаблонов"
msgstr "Your bot already has too many templates"
#: olgram/commands/menu.py:390
msgid "Такой текст уже есть в списке шаблонов"
msgstr "This text is already in the list of templates"
#: olgram/commands/menu.py:408
msgid "У вас нет прав на этого бота"
msgstr "You have no permissions to this bot"
#: olgram/commands/promo.py:27
msgid ""
"Новый промокод\n"
"```{0}```"
msgstr ""
"New promo code\n"
"```{0}```"
#: olgram/commands/promo.py:46
msgid "Неправильный токен"
msgstr "Incorrect token"
#: olgram/commands/promo.py:49
msgid "Такого кода не существует"
msgstr "There is no such code"
#: olgram/commands/promo.py:59
msgid "Промокод отозван"
msgstr "Promotion code withdrawn"
#: olgram/commands/promo.py:70
msgid ""
"Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>"
msgstr ""
"Specify the argument: promo code. For example: <pre>/setpromo my-promo-"
"code</pre>"
#: olgram/commands/promo.py:78 olgram/commands/promo.py:82
msgid "Промокод не найден"
msgstr "Promo code not found"
#: olgram/commands/promo.py:85
msgid "Промокод уже использован"
msgstr "Promo code has already been used"
#: olgram/commands/promo.py:91
msgid "Промокод активирован! Спасибо 🙌"
msgstr "Promo code activated! Thank you 🙌"
#: olgram/commands/start.py:23
msgid ""
"\n"
" Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее "
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>. Следите за "
"обновлениями <a href=\"https://t.me/civsoc_it\">здесь</a>.\n"
"\n"
" Используйте эти команды, чтобы управлять этим ботом:\n"
"\n"
" /addbot - добавить бот\n"
" /mybots - управление ботами\n"
"\n"
" /help - помощь\n"
" "
msgstr ""
"\n"
" Olgram Bot is a feedback bot contructor for Telegram. More info <a "
"href=\"https://olgram.readthedocs.io\">here</a>.\n"
"\n"
" Use that commands to control bot:\n"
"\n"
" /addbot - add bot\n"
" /mybots - bot control\n"
"\n"
" /help - help\n"
" "
#: olgram/commands/start.py:43
msgid ""
"\n"
" Читайте инструкции на нашем сайте https://olgram.readthedocs.io\n"
" Техническая поддержка: @civsocit_feedback_bot\n"
" Версия {0}\n"
" "
msgstr ""
"\n"
" Read the instructions on our website at https://olgram.readthedocs.io\n"
" Technical support: @civsocit_feedback_bot\n"
" Version {0}\n"
" "
#: olgram/models/models.py:30
msgid ""
"\n"
" Здравствуйте!\n"
" Напишите ваш вопрос и мы ответим вам в ближайшее время.\n"
" "
msgstr ""
"\n"
" Hello!\n"
" Write your question and we will answer you shortly.\n"
" "
#: olgram/utils/permissions.py:40
msgid "Владелец бота ограничил доступ к этому функционалу 😞"
msgstr "The bot owner has restricted access to this functionality 😞"
#: olgram/utils/permissions.py:52
msgid "Владелец бота ограничил доступ к этому функционалу😞"
msgstr "The owner of the bot has restricted access to this function😞"
#: server/custom.py:55
msgid ""
"<b>Политика конфиденциальности</b>\n"
"\n"
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
"отправке сообщения (кроме команд /start и /security_policy) ваш "
"идентификатор пользователя записывается в кеш на некоторое время и потом "
"удаляется из кеша. Этот идентификатор используется только для общения с "
"оператором; боты Olgram не делают массовых рассылок.\n"
"\n"
msgstr ""
"<b>Privacy Policy</b>.\n"
"\n"
"This bot does not store your messages, username and @username. When you send "
"a message (except for /start and /security_policy), your username is cached "
"for a while and then deleted from the cache. This ID is only used for "
"communicating with the operator; Olgram bots do not do mass mailings.\n"
"\n"
#: server/custom.py:61
msgid ""
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
"силу настроек, которые оператор указал при создании бота."
msgstr ""
"When sending a message (except /start and /security_policy), the operator "
"<b>sees</b> your username, @username and user ID by virtue of the settings "
"that the operator specified when creating the bot."
#: server/custom.py:65
msgid ""
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
"видеть ваш username, имя пользователя и другую информацию."
msgstr ""
"Depending on your Telegram privacy settings, the operator may see your "
"username, username and other information."
#: server/custom.py:76
msgid "Сообщение от пользователя "
msgstr "Message from the user "
#: server/custom.py:135
msgid "Вы заблокированы в этом боте"
msgstr "You are blocked in this bot"
#: server/custom.py:141
msgid "Слишком много сообщений, подождите одну минуту"
msgstr "Too many messages, wait one minute"
#: server/custom.py:148
msgid "Не удаётся связаться с владельцем бота"
msgstr "Cannot contact the owner of the bot"
#: server/custom.py:179
msgid ""
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
"старое?)</i>"
msgstr ""
"<i>Cannot forward this message: author not found (message too old?)</i>"
#: server/custom.py:187
msgid "Пользователь заблокирован"
msgstr "User is blocked"
#: server/custom.py:192
msgid "Пользователь не был забанен"
msgstr "The user was not banned"
#: server/custom.py:195
msgid "Пользователь разбанен"
msgstr "A user has been unlocked"
#: server/custom.py:200
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
msgstr "<i>Cannot forward the message (has the author blocked the bot?)</i>"
#: server/server.py:41
msgid "(Пере)запустить бота"
msgstr "(Re)launch the bot"
#: server/server.py:42
msgid "Политика конфиденциальности"
msgstr "Privacy Policy"
msgid ""
"\n"
"\n"
"Этот бот создан с помощью @OlgramBot"
msgstr ""
"\n"
"\n"
"This bot was created using @OlgramBot"

25
locales/locale.py Normal file
View 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,
}

View File

@@ -0,0 +1,727 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2022-09-02 05:07+0400\n"
"PO-Revision-Date: 2022-09-02 05:12+0400\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: uk_UA\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.1\n"
#: olgram/commands/admin.py:21 olgram/commands/info.py:21
#: olgram/commands/promo.py:23 olgram/commands/promo.py:39
msgid "Недостаточно прав"
msgstr "Недостатньо прав"
#: olgram/commands/admin.py:27
msgid "Нужно указать имя бота"
msgstr "Потрібно вказати ім'я бота"
#: olgram/commands/admin.py:33
msgid "Такого бота нет в системе"
msgstr "Такого бота немає в системі"
#: olgram/commands/admin.py:39 olgram/commands/admin.py:53
msgid "Пропустить"
msgstr "Пропустити"
#: olgram/commands/admin.py:42
msgid ""
"Введите текст, который будет отправлен владельцу бота {0}. Напишите "
"'Пропустить' чтобы отменить"
msgstr ""
"Введіть текст, який буде надіслано власнику бота {0}. Напишіть 'Пропустити', "
"щоб скасувати"
#: olgram/commands/admin.py:50
msgid "Поддерживается только текст"
msgstr "Підтримується лише текст"
#: olgram/commands/admin.py:55 olgram/commands/admin.py:71
msgid "Отменено"
msgstr "Скасовано"
#: olgram/commands/admin.py:61 olgram/commands/admin.py:69
msgid "Отправить"
msgstr "Надіслати"
#: olgram/commands/admin.py:62
msgid "Отменить"
msgstr "Скасувати"
#: olgram/commands/admin.py:81
msgid "Отправлено"
msgstr "Надіслано"
#: olgram/commands/bot_actions.py:22
msgid "Бот удалён"
msgstr "Бот видалений"
#: olgram/commands/bot_actions.py:38 olgram/commands/bot_actions.py:50
msgid "Текст сброшен"
msgstr "Текст скинутий"
#: olgram/commands/bot_actions.py:64
msgid "Выбран личный чат"
msgstr "Вибраний особистий чат"
#: olgram/commands/bot_actions.py:77
msgid "Бот вышел из чатов"
msgstr "Бот вийшов із чатів"
#: olgram/commands/bot_actions.py:83
msgid "Нельзя привязать бота к этому чату"
msgstr "Не можна прив'язати робота до цього чату"
#: olgram/commands/bot_actions.py:87
msgid "Выбран чат {0}"
msgstr "Вибраний чат {0}"
#: olgram/commands/bots.py:46
msgid ""
"У вас уже слишком много ботов. Удалите какой-нибудь свой бот из Olgram(/"
"mybots -> (Выбрать бота) -> Удалить бот)"
msgstr ""
"У вас вже надто багато роботів. Видаліть якийсь свій бот з Olgram(/mybots -> "
"(Вибрати бота) -> Видалити бот)"
#: olgram/commands/bots.py:50
msgid ""
"\n"
" Чтобы подключить бот, вам нужно выполнить три действия:\n"
"\n"
" 1. Перейдите в бот @BotFather, нажмите START и отправьте команду /"
"newbot\n"
" 2. Введите название бота, а потом username бота.\n"
" 3. После создания бота перешлите ответное сообщение в этот бот или "
"скопируйте и пришлите token бота.\n"
"\n"
" Важно: не подключайте боты, которые используются в других сервисах "
"(Manybot, Chatfuel, Livegram и других).\n"
" "
msgstr ""
"\n"
" Щоб підключити бот, вам потрібно виконати три дії:\n"
"\n"
" 1. Перейдіть до бот @BotFather, натисніть START і надішліть команду /"
"newbot\n"
" 2. Введіть назву бота, а потім username бота.\n"
" 3. Після створення бота перешліть повідомлення у цей бот або скопіюйте "
"і надішліть token бота.\n"
"\n"
" Важливо: не підключайте роботи, які використовуються в інших сервісах "
"(Manybot, Chatfuel, Livegram та інших).\n"
" \n"
" "
#: olgram/commands/bots.py:70
msgid ""
"\n"
" Это не токен бота.\n"
"\n"
" Токен выглядит вот так: 123456789:AAAA-"
"abc123_AbcdEFghijKLMnopqrstu12\n"
" "
msgstr ""
"\n"
" Це не токен робота.\n"
"\n"
" Токен виглядає ось так: 123456789:AAAA-"
"abc123_AbcdEFghijKLMnopqrstu12\n"
" "
#: olgram/commands/bots.py:77
msgid ""
"\n"
" Не удалось запустить этого бота: неверный токен\n"
" "
msgstr ""
"\n"
" Не вдалося запустити цього бота: неправильний токен\n"
" "
#: olgram/commands/bots.py:82
msgid ""
"\n"
" Не удалось запустить этого бота: непредвиденная ошибка\n"
" "
msgstr ""
"\n"
" Не вдалося запустити цього бота: непередбачена помилка\n"
" "
#: olgram/commands/bots.py:87
msgid ""
"\n"
" Такой бот уже есть в базе данных\n"
" "
msgstr ""
"\n"
" Такий бот вже є у базі даних\n"
" "
#: olgram/commands/bots.py:122
msgid "Бот добавлен! Список ваших ботов: /mybots"
msgstr "Бот доданий! Список ваших роботів: /mybots"
#: olgram/commands/info.py:34
msgid "Количество ботов: {0}\n"
msgstr "Кількість ботів: {0}\n"
#: olgram/commands/info.py:35
msgid "Количество пользователей (у конструктора): {0}\n"
msgstr "Кількість користувачів (у конструктора): {0}\n"
#: olgram/commands/info.py:36
msgid "Шаблонов ответов: {0}\n"
msgstr "Шаблонів відповідей: {0}\n"
#: olgram/commands/info.py:37
msgid "Входящих сообщений у всех ботов: {0}\n"
msgstr "Вхідних повідомлень у всіх роботів: {0}\n"
#: olgram/commands/info.py:38
msgid "Исходящих сообщений у всех ботов: {0}\n"
msgstr "Вихідних повідомлень у всіх роботів: {0}\n"
#: olgram/commands/info.py:39
msgid "Промо-кодов выдано: {0}\n"
msgstr "Промо-кодів видано: {0}\n"
#: olgram/commands/menu.py:31
msgid ""
"\n"
" У вас нет добавленных ботов.\n"
"\n"
" Отправьте команду /addbot, чтобы добавить бот.\n"
" "
msgstr ""
"\n"
" У вас немає доданих роботів.\n"
"\n"
" Надішліть команду /addbot, щоб додати бот.\n"
" \n"
" "
#: olgram/commands/menu.py:46
msgid "Ваши боты"
msgstr "Ваші боти"
#: olgram/commands/menu.py:67
msgid "Личные сообщения"
msgstr "Особисті повідомлення"
#: olgram/commands/menu.py:72
msgid "❗️ Выйти из всех чатов"
msgstr "❗️ Вийти зі всіх чатів"
#: olgram/commands/menu.py:77 olgram/commands/menu.py:122
#: olgram/commands/menu.py:148 olgram/commands/menu.py:184
#: olgram/commands/menu.py:247
msgid "<< Назад"
msgstr "<< Назад"
#: olgram/commands/menu.py:83
msgid ""
"\n"
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
"вам в бот.\n"
" Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню "
"ещё раз и выберите добавленный чат.\n"
" Если ваш бот состоял в групповом чате до того, как его добавили в "
"Olgram - удалите бота из чата и добавьте\n"
" снова.\n"
" "
msgstr ""
"\n"
" Цей бот не доданий до чатів, тому всі повідомлення будуть приходити "
"вам у бот.\n"
" Щоб підключити чат — додайте бот @{0} до чату, відкрийте це меню ще "
"раз і виберіть доданий чат.\n"
" Якщо ваш бот перебував у груповому чаті до того, як його додали до "
"Olgram - видаліть бота з чату та додайте\n"
" знову.\n"
" \n"
" "
#: olgram/commands/menu.py:90
msgid ""
"\n"
" В этом разделе вы можете привязать бота @{0} к чату.\n"
" Выберите чат, куда бот будет пересылать сообщения.\n"
" "
msgstr ""
"\n"
" У цьому розділі ви можете прив'язати робота @{0} до чату.\n"
" Виберіть чат, куди бот пересилатиме повідомлення.\n"
" \n"
" "
#: olgram/commands/menu.py:102
msgid "Текст"
msgstr "Текст"
#: olgram/commands/menu.py:107
msgid "Чат"
msgstr "Чат"
#: olgram/commands/menu.py:112
msgid "Удалить бот"
msgstr "Видалити бот"
#: olgram/commands/menu.py:117
msgid "Статистика"
msgstr "Статистика"
#: olgram/commands/menu.py:126
msgid "Опции"
msgstr "Опції"
#: olgram/commands/menu.py:131
msgid ""
"\n"
" Управление ботом @{0}.\n"
"\n"
" Если у вас возникли вопросы по настройке бота, то посмотрите нашу "
"справку /help или напишите нам\n"
" @civsocit_feedback_bot\n"
" "
msgstr ""
"\n"
" Управління роботом @{0}.\n"
"\n"
" Якщо у вас виникли питання з налаштування бота, подивіться нашу довідку /"
"help або напишіть нам\n"
" @civsocit_feedback_bot\n"
" "
#: olgram/commands/menu.py:143
msgid "Да, удалить бот"
msgstr "Так, видалити бот"
#: olgram/commands/menu.py:152
msgid ""
"\n"
" Вы уверены, что хотите удалить бота @{0}?\n"
" "
msgstr ""
"\n"
" Ви впевнені, що хочете видалити бота @{0}?\n"
" "
#: olgram/commands/menu.py:161
msgid "Потоки сообщений"
msgstr "Потоки повідомлень"
#: olgram/commands/menu.py:166
msgid "Данные пользователя"
msgstr "Дані користувача"
#: olgram/commands/menu.py:171
msgid "Антифлуд"
msgstr "Антифлуд"
#: olgram/commands/menu.py:178
msgid "Olgram подпись"
msgstr "Olgram підпис"
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
msgid "включены"
msgstr "включені"
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
msgid "выключены"
msgstr "вимкнені"
#: olgram/commands/menu.py:191
#, fuzzy
#| msgid "включены"
msgid "включен"
msgstr "включені"
#: olgram/commands/menu.py:191
#, fuzzy
#| msgid "выключены"
msgid "выключен"
msgstr "вимкнені"
#: olgram/commands/menu.py:192
msgid ""
"\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#threads\">Потоки сообщений</a>: <b>{0}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
"info\">Данные пользователя</a>: <b>{1}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#antiflood\">Антифлуд</a>: <b>{2}</b>\n"
" "
msgstr ""
"\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#threads\">Потоки повідомлень</a>: <b>{0}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
"info\">Дані користувача</a>: <b>{1}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#antiflood\">Anti-flood</a>: <b>{2}</b>"
#: olgram/commands/menu.py:199
msgid "включена"
msgstr "включена"
#: olgram/commands/menu.py:199
msgid "выключена"
msgstr "вимкнена"
#: olgram/commands/menu.py:200
msgid "Olgram подпись: <b>{0}</b>"
msgstr "Olgram підпис: <b>{0}</b>"
#: olgram/commands/menu.py:210 olgram/commands/menu.py:272
#: olgram/commands/menu.py:314
msgid "<< Завершить редактирование"
msgstr "<< Завершити редагування"
#: olgram/commands/menu.py:214
msgid "Автоответчик"
msgstr "Автовідповідач"
#: olgram/commands/menu.py:219 olgram/commands/menu.py:286
msgid "Сбросить текст"
msgstr "Скинути текст"
#: olgram/commands/menu.py:224
msgid ""
"\n"
" Сейчас вы редактируете текст, который отправляется после того, как "
"пользователь отправит вашему боту @{0}\n"
" команду /start\n"
"\n"
" Текущий текст:\n"
" <pre>{1}</pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n"
" "
msgstr ""
"\n"
" Зараз ви редагуєте текст, який надсилається після того, як користувач "
"надішле вашому боту @{0}\n"
" команду /start\n"
"\n"
" Поточний текст:\n"
" <pre>{1}</pre>\n"
" Надішліть повідомлення, щоб змінити текст.\n"
" \n"
" "
#: olgram/commands/menu.py:251
msgid ""
"\n"
" Статистика по боту @{0}\n"
"\n"
" Входящих сообщений: <b>{1}</b>\n"
" Ответных сообщений: <b>{2}</b>\n"
" Шаблоны ответов: <b>{3}</b>\n"
" Забанено пользователей: <b>{4}</b>\n"
" "
msgstr ""
"\n"
" Статистика з роботи @{0}\n"
"\n"
" Вхідних повідомлень: <b>{1}</b>\n"
" Повідомлень у відповідь: <b>{2}</b>\n"
" Шаблони відповідей: <b>{3}</b>\n"
" Забанено користувачів: <b>{4}</b>\n"
" "
#: olgram/commands/menu.py:276
msgid "Предыдущий текст"
msgstr "Попередній текст"
#: olgram/commands/menu.py:281
msgid "Шаблоны ответов..."
msgstr "Шаблони відповідей..."
#: olgram/commands/menu.py:291
msgid ""
"\n"
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
"отключено.\n"
"\n"
" Текущий текст:\n"
" <pre>{1}</pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n"
" "
msgstr ""
"\n"
" Зараз ви редагуєте текст автовідповідача. Це повідомлення надсилається у "
"відповідь на всі вхідні повідомлення @{0} автоматично. За замовчуванням його "
"вимкнено.\n"
"\n"
" Поточний текст:\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Надішліть повідомлення, щоб змінити текст.\n"
" "
#: olgram/commands/menu.py:301
msgid "(отключено)"
msgstr "(відключено)"
#: olgram/commands/menu.py:318
msgid ""
"\n"
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
"\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте какую-нибудь фразу (например: \"Ваш заказ готов, ожидайте!\"), "
"чтобы добавить её в шаблон.\n"
" Чтобы удалить шаблон из списка, отправьте его номер в списке (например, "
"4)\n"
" "
msgstr ""
"\n"
" Зараз ви редагуєте шаблони для @{0}. Поточні шаблони:\n"
"\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Надішліть якусь фразу (наприклад: \"Ваше замовлення готове, чекайте!\"), "
"щоб додати її до шаблону.\n"
" Щоб видалити шаблон зі списку, відправте його у списку (наприклад, 4)\n"
" \n"
" "
#: olgram/commands/menu.py:337
msgid "(нет шаблонов)"
msgstr "(Немає шаблонів)"
#: olgram/commands/menu.py:376
msgid "У вас нет шаблонов, чтобы их удалять"
msgstr "У вас немає шаблонів, щоб їх видаляти"
#: olgram/commands/menu.py:378
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
msgstr "Неправильне число. Щоб видалити шаблон, введіть число від 0 до {0}"
#: olgram/commands/menu.py:386
msgid "У вашего бота уже слишком много шаблонов"
msgstr "У вашого бота вже дуже багато шаблонів"
#: olgram/commands/menu.py:390
msgid "Такой текст уже есть в списке шаблонов"
msgstr "Такий текст вже є у списку шаблонів"
#: olgram/commands/menu.py:408
msgid "У вас нет прав на этого бота"
msgstr "У вас немає прав на цього бота"
#: olgram/commands/promo.py:27
msgid ""
"Новый промокод\n"
"```{0}```"
msgstr ""
"Новий промокод\n"
"```{0}```"
#: olgram/commands/promo.py:46
msgid "Неправильный токен"
msgstr "Неправильний токен"
#: olgram/commands/promo.py:49
msgid "Такого кода не существует"
msgstr "Такого коду не існує"
#: olgram/commands/promo.py:59
msgid "Промокод отозван"
msgstr "Промокод відкликаний"
#: olgram/commands/promo.py:70
msgid ""
"Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>"
msgstr ""
"Зазначте аргумент: промокод. Наприклад: <pre>/setpromo my-promo-code</pre>"
#: olgram/commands/promo.py:78 olgram/commands/promo.py:82
msgid "Промокод не найден"
msgstr "Промокод не знайдено"
#: olgram/commands/promo.py:85
msgid "Промокод уже использован"
msgstr "Промокод уже використаний"
#: olgram/commands/promo.py:91
msgid "Промокод активирован! Спасибо 🙌"
msgstr "Промокод активовано! Дякую 🙌"
#: olgram/commands/start.py:23
msgid ""
"\n"
" Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее "
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>. Следите за "
"обновлениями <a href=\"https://t.me/civsoc_it\">здесь</a>.\n"
"\n"
" Используйте эти команды, чтобы управлять этим ботом:\n"
"\n"
" /addbot - добавить бот\n"
" /mybots - управление ботами\n"
"\n"
" /help - помощь\n"
" "
msgstr ""
"\n"
" Olgram Bot - це конструктор роботів зворотного зв'язку в Telegram. "
"Докладніше <a href=\"https://olgram.readthedocs.io\">читайте тут</a>. "
"Слідкуйте за оновленнями <a href=\"https://t.me/civsoc_it\">тут</a>.\n"
"\n"
" Використовуйте ці команди, щоб керувати цим ботом:\n"
"\n"
" /addbot - додати бот\n"
" /mybots - керування ботами\n"
"\n"
" /help - допомога\n"
" \n"
" "
#: olgram/commands/start.py:43
msgid ""
"\n"
" Читайте инструкции на нашем сайте https://olgram.readthedocs.io\n"
" Техническая поддержка: @civsocit_feedback_bot\n"
" Версия {0}\n"
" "
msgstr ""
"\n"
" Читайте інструкції на нашому сайті https://olgram.readthedocs.io\n"
" Технічна підтримка: @civsocit_feedback_bot\n"
" Версія {0}\n"
" \n"
" "
#: olgram/models/models.py:30
msgid ""
"\n"
" Здравствуйте!\n"
" Напишите ваш вопрос и мы ответим вам в ближайшее время.\n"
" "
msgstr ""
"\n"
" Доброго дня!\n"
" Напишіть ваше запитання, і ми відповімо вам найближчим часом.\n"
" \n"
" "
#: olgram/utils/permissions.py:40
msgid "Владелец бота ограничил доступ к этому функционалу 😞"
msgstr "Власник бота обмежив доступ до цього функціоналу 😞"
#: olgram/utils/permissions.py:52
msgid "Владелец бота ограничил доступ к этому функционалу😞"
msgstr "Власник бота обмежив доступ до цього функціоналу 😞"
#: server/custom.py:55
msgid ""
"<b>Политика конфиденциальности</b>\n"
"\n"
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
"отправке сообщения (кроме команд /start и /security_policy) ваш "
"идентификатор пользователя записывается в кеш на некоторое время и потом "
"удаляется из кеша. Этот идентификатор используется только для общения с "
"оператором; боты Olgram не делают массовых рассылок.\n"
"\n"
msgstr ""
"<b>Політика конфіденційності</b>\n"
"\n"
"Цей бот не зберігає ваші повідомлення, ім'я користувача та @username. При "
"надсиланні повідомлення (крім команд /start та /security_policy) ваш "
"ідентифікатор користувача записується в кеш на деякий час і потім "
"видаляється з кеша. Цей ідентифікатор використовується лише для спілкування "
"з оператором; боти Olgram не роблять масових розсилок.\n"
"\n"
#: server/custom.py:61
msgid ""
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
"силу настроек, которые оператор указал при создании бота."
msgstr ""
"При надсиланні повідомлення (крім команд /start та /security_policy) "
"оператор <b>бачить</b> ваше ім'я користувача, @username та ідентифікатор "
"користувача через налаштування, які оператор вказав при створенні бота."
#: server/custom.py:65
msgid ""
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
"видеть ваш username, имя пользователя и другую информацию."
msgstr ""
"Залежно від ваших налаштувань конфіденційності Telegram оператор може бачити "
"ваш username, ім'я користувача та іншу інформацію."
#: server/custom.py:76
msgid "Сообщение от пользователя "
msgstr "Допис від користувача "
#: server/custom.py:135
msgid "Вы заблокированы в этом боте"
msgstr "Ви заблоковані у цьому боті"
#: server/custom.py:141
msgid "Слишком много сообщений, подождите одну минуту"
msgstr "Забагато повідомлень, зачекайте одну хвилину"
#: server/custom.py:148
msgid "Не удаётся связаться с владельцем бота"
msgstr "Не вдається зв'язатися з власником бота"
#: server/custom.py:179
msgid ""
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
"старое?)</i>"
msgstr ""
"<i>Неможливо надіслати повідомлення: автора не знайдено (повідомлення "
"занадто старе?)</i>"
#: server/custom.py:187
msgid "Пользователь заблокирован"
msgstr "Користувач заблоковано"
#: server/custom.py:192
msgid "Пользователь не был забанен"
msgstr "Користувач не був забанений"
#: server/custom.py:195
msgid "Пользователь разбанен"
msgstr "Користувач розбанений"
#: server/custom.py:200
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
msgstr "<i>Неможливо надіслати повідомлення (автор заблокував робота?)</i>"
#: server/server.py:41
msgid "(Пере)запустить бота"
msgstr "(Пере) запустити бота"
#: server/server.py:42
msgid "Политика конфиденциальности"
msgstr "Політика конфіденційності"
msgid ""
"\n"
"\n"
"Этот бот создан с помощью @OlgramBot"
msgstr ""
"\n"
"\n"
"Цей бот створено за допомогою @OlgramBot"

View 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 "

32
main.py
View File

@@ -11,12 +11,13 @@ import olgram.commands.bots # noqa: F401
import olgram.commands.start # noqa: F401
import olgram.commands.menu # noqa: F401
import olgram.commands.bot_actions # noqa: F401
import olgram.commands.info # noqa: F401
import olgram.commands.promo # noqa: F401
import olgram.commands.admin # noqa: F401
from locales.locale import _
from server.server import main as server_main
import logging
logging.basicConfig(level=logging.INFO)
async def init_database():
await Tortoise.init(config=TORTOISE_ORM)
@@ -24,14 +25,14 @@ async def init_database():
async def init_olgram():
from olgram.router import bot, dp
dp.setup_middleware(AccessMiddleware(OlgramSettings.admin_id()))
dp.setup_middleware(AccessMiddleware(OlgramSettings.admin_ids()))
from aiogram.types import BotCommand
await bot.set_my_commands(
[
BotCommand("start", "Запустить бота"),
BotCommand("addbot", "Добавить бот"),
BotCommand("mybots", "Управление ботами"),
BotCommand("help", "Справка")
BotCommand("start", _("Запустить бота")),
BotCommand("addbot", _("Добавить бот")),
BotCommand("mybots", _("Управление ботами")),
BotCommand("help", _("Справка"))
]
)
@@ -43,19 +44,20 @@ async def initialization():
def main():
"""
Classic polling
"""
parser = argparse.ArgumentParser("Olgram server")
parser.add_argument("--noserver", help="Не запускать сервер обратной связи, только сам Olgram (режим для "
"разработки)", action="store_true")
parser = argparse.ArgumentParser("Olgram bot and feedback server")
group = parser.add_mutually_exclusive_group()
group.add_argument("--noserver", help="Не запускать сервер обратной связи, только сам Olgram", action="store_true")
group.add_argument("--onlyserver", help="Запустить только сервер обратной связи, без Olgram", action="store_true")
args = parser.parse_args()
loop = asyncio.get_event_loop()
loop.run_until_complete(initialization())
loop.create_task(dp.start_polling())
if not args.onlyserver:
print("Run olgram polling")
loop.create_task(dp.start_polling())
if not args.noserver:
print("Run olgram server")
loop.create_task(server_main().start())
loop.run_forever()

View File

@@ -1,9 +1,6 @@
import asyncio
import logging
from olgram.migrations.custom import migrate
logging.basicConfig(level=logging.INFO)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(migrate())

81
olgram/commands/admin.py Normal file
View 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())

View File

@@ -3,8 +3,10 @@
"""
from aiogram import types
from aiogram.utils.exceptions import TelegramAPIError, Unauthorized
from olgram.models.models import Bot
from aiogram import Bot as AioBot
from olgram.models.models import Bot, BotStartMessage, BotSecondMessage
from server.server import unregister_token
from locales.locale import _
async def delete_bot(bot: Bot, call: types.CallbackQuery):
@@ -17,35 +19,47 @@ async def delete_bot(bot: Bot, call: types.CallbackQuery):
# Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы
pass
await bot.delete()
await call.answer("Бот удалён")
await call.answer(_("Бот удалён"))
try:
await call.message.delete()
except TelegramAPIError:
pass
async def reset_bot_text(bot: Bot, call: types.CallbackQuery):
async def reset_bot_text(bot: Bot, call: types.CallbackQuery, state):
"""
Пользователь решил сбросить текст бота к default
:param bot:
:param call:
:return:
"""
bot.start_text = bot._meta.fields_map['start_text'].default
await bot.save()
await call.answer("Текст сброшен")
async with state.proxy() as proxy:
lang = proxy.get("lang", "none")
if lang == "none":
await BotStartMessage.filter(bot=bot).delete()
bot.start_text = bot._meta.fields_map['start_text'].default
await bot.save(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):
async def reset_bot_second_text(bot: Bot, call: types.CallbackQuery, state):
"""
Пользователь решил сбросить second text бота
:param bot:
:param call:
:return:
"""
bot.second_text = bot._meta.fields_map['second_text'].default
await bot.save()
await call.answer("Текст сброшен")
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):
@@ -59,13 +73,58 @@ async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str):
if chat == "personal":
bot.group_chat = None
await bot.save()
await call.answer("Выбран личный чат")
await call.answer(_("Выбран личный чат"))
return
if chat == "leave":
bot.group_chat = None
await bot.save()
chats = await bot.group_chats.all()
a_bot = AioBot(bot.decrypted_token())
for chat in chats:
try:
await chat.delete()
await a_bot.leave_chat(chat.chat_id)
except TelegramAPIError:
pass
await call.answer(_("Бот вышел из чатов"))
await a_bot.session.close()
return
chat_obj = await bot.group_chats.filter(id=chat).first()
if not chat_obj:
await call.answer("Нельзя привязать бота к этому чату")
await call.answer(_("Нельзя привязать бота к этому чату"))
return
bot.group_chat = chat_obj
await bot.save()
await call.answer(f"Выбран чат {chat_obj.name}")
await call.answer(_("Выбран чат {0}").format(chat_obj.name))
async def threads(bot: Bot, call: types.CallbackQuery):
bot.enable_threads = not bot.enable_threads
await bot.save(update_fields=["enable_threads"])
async def additional_info(bot: Bot, call: types.CallbackQuery):
bot.enable_additional_info = not bot.enable_additional_info
await bot.save(update_fields=["enable_additional_info"])
async def 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"])

View File

@@ -9,9 +9,10 @@ import re
from textwrap import dedent
from olgram.models.models import Bot, User
from olgram.settings import OlgramSettings
from olgram.settings import OlgramSettings, BotSettings
from olgram.commands.menu import send_bots_menu
from server.server import register_token
from locales.locale import _
from olgram.router import dp
@@ -36,12 +37,17 @@ async def add_bot(message: types.Message, state: FSMContext):
"""
Команда /addbot (добавить бота)
"""
user = await User.get_or_none(telegram_id=message.from_user.id)
max_bot_count = OlgramSettings.max_bots_per_user()
if user and await user.is_promo():
max_bot_count = OlgramSettings.max_bots_per_user_promo()
bot_count = await Bot.filter(owner__telegram_id=message.from_user.id).count()
if bot_count >= OlgramSettings.max_bots_per_user():
await message.answer("У вас уже слишком много ботов.")
if bot_count >= max_bot_count:
await message.answer(_("У вас уже слишком много ботов. Удалите какой-нибудь свой бот из Olgram"
"(/mybots -> (Выбрать бота) -> Удалить бот)"))
return
await message.answer(dedent("""
await message.answer(dedent(_("""
Чтобы подключить бот, вам нужно выполнить три действия:
1. Перейдите в бот @BotFather, нажмите START и отправьте команду /newbot
@@ -49,7 +55,7 @@ async def add_bot(message: types.Message, state: FSMContext):
3. После создания бота перешлите ответное сообщение в этот бот или скопируйте и пришлите token бота.
Важно: не подключайте боты, которые используются в других сервисах (Manybot, Chatfuel, Livegram и других).
"""))
""")))
await state.set_state("add_bot")
@@ -61,26 +67,26 @@ async def bot_added(message: types.Message, state: FSMContext):
token = re.findall(token_pattern, message.text)
async def on_invalid_token():
await message.answer(dedent("""
await message.answer(dedent(_("""
Это не токен бота.
Токен выглядит вот так: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12
"""))
""")))
async def on_dummy_token():
await message.answer(dedent("""
await message.answer(dedent(_("""
Не удалось запустить этого бота: неверный токен
"""))
""")))
async def on_unknown_error():
await message.answer(dedent("""
await message.answer(dedent(_("""
Не удалось запустить этого бота: непредвиденная ошибка
"""))
""")))
async def on_duplication_bot():
await message.answer(dedent("""
await message.answer(dedent(_("""
Такой бот уже есть в базе данных
"""))
""")))
if not token:
return await on_invalid_token()
@@ -98,7 +104,10 @@ async def bot_added(message: types.Message, state: FSMContext):
except TelegramAPIError:
return await on_unknown_error()
user, _ = await User.get_or_create(telegram_id=message.from_user.id)
if token == BotSettings.token():
return await on_duplication_bot()
user, created = await User.get_or_create(telegram_id=message.from_user.id)
bot = Bot(token=Bot.encrypted_token(token), owner=user, name=test_bot_info.username,
super_chat_id=message.from_user.id)
try:
@@ -110,5 +119,5 @@ async def bot_added(message: types.Message, state: FSMContext):
await bot.delete()
return await on_unknown_error()
await message.answer("Бот добавлен! Список ваших ботов: /mybots")
await message.answer(_("Бот добавлен! Список ваших ботов: /mybots"))
await state.reset_state()

40
olgram/commands/info.py Normal file
View 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))

View File

@@ -1,12 +1,13 @@
from olgram.router import dp
from aiogram import types, Bot as AioBot
from olgram.models.models import Bot, User
from olgram.models.models import Bot, User, DefaultAnswer, BotStartMessage, BotSecondMessage
from aiogram.dispatcher import FSMContext
from aiogram.utils.callback_data import CallbackData
from textwrap import dedent
from olgram.utils.mix import edit_or_create, button_text_limit
from olgram.utils.mix import edit_or_create, button_text_limit, wrap
from olgram.commands import bot_actions
from locales.locale import _
import typing as ty
@@ -27,11 +28,11 @@ async def send_bots_menu(chat_id: int, user_id: int, call=None):
user = await User.get_or_none(telegram_id=user_id)
bots = await Bot.filter(owner=user)
if not bots:
await AioBot.get_current().send_message(chat_id, dedent("""
await AioBot.get_current().send_message(chat_id, dedent(_("""
У вас нет добавленных ботов.
Отправьте команду /addbot, чтобы добавить бот.
"""))
""")))
return
keyboard = types.InlineKeyboardMarkup(row_width=2)
@@ -42,7 +43,7 @@ async def send_bots_menu(chat_id: int, user_id: int, call=None):
chat=empty))
)
text = "Ваши боты"
text = _("Ваши боты")
if call:
await edit_or_create(call, text, keyboard)
else:
@@ -63,28 +64,33 @@ async def send_chats_menu(bot: Bot, call: types.CallbackQuery):
)
if chats:
keyboard.insert(
types.InlineKeyboardButton(text="Личные сообщения",
types.InlineKeyboardButton(text=_("Личные сообщения"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
chat="personal"))
)
keyboard.insert(
types.InlineKeyboardButton(text=_("❗️ Выйти из всех чатов"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
chat="leave"))
)
keyboard.insert(
types.InlineKeyboardButton(text="<< Назад",
types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty,
chat=empty))
)
if not chats:
text = dedent(f"""
text = dedent(_("""
Этот бот не добавлен в чаты, поэтому все сообщения будут приходить вам в бот.
Чтобы подключить чат — добавьте бот @{bot.name} в чат, откройте это меню ещё раз и выберите добавленный чат.
Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню ещё раз и выберите добавленный чат.
Если ваш бот состоял в групповом чате до того, как его добавили в Olgram - удалите бота из чата и добавьте
снова.
""")
""")).format(bot.name)
else:
text = dedent(f"""
В этом разделе вы можете привязать бота @{bot.name} к чату.
text = dedent(_("""
В этом разделе вы можете привязать бота @{0} к чату.
Выберите чат, куда бот будет пересылать сообщения.
""")
""")).format(bot.name)
await edit_or_create(call, text, keyboard)
@@ -93,81 +99,215 @@ async def send_bot_menu(bot: Bot, call: types.CallbackQuery):
await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert(
types.InlineKeyboardButton(text="Текст",
types.InlineKeyboardButton(text=_("Текст"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text="Чат",
types.InlineKeyboardButton(text=_("Чат"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="chat",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text="Удалить бот",
types.InlineKeyboardButton(text=_("Удалить бот"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="delete",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text="<< Назад",
types.InlineKeyboardButton(text=_("Статистика"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="stat",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=0, bot_id=empty, operation=empty, chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text=_("Опции"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="settings",
chat=empty))
)
await edit_or_create(call, dedent(f"""
Управление ботом @{bot.name}.
await edit_or_create(call, dedent(_("""
Управление ботом @{0}.
Если у вас возникли вопросы по настройке бота, то посмотрите нашу справку /help или напишите нам
@civsocit_feedback_bot
"""), reply_markup=keyboard)
""")).format(bot.name), reply_markup=keyboard)
async def send_bot_delete_menu(bot: Bot, call: types.CallbackQuery):
await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert(
types.InlineKeyboardButton(text="Да, удалить бот",
types.InlineKeyboardButton(text=_("Да, удалить бот"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="delete_yes",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text="<< Назад",
types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
)
await edit_or_create(call, dedent(f"""
Вы уверены, что хотите удалить бота @{bot.name}?
"""), reply_markup=keyboard)
await edit_or_create(call, dedent(_("""
Вы уверены, что хотите удалить бота @{0}?
""")).format(bot.name), reply_markup=keyboard)
async def send_bot_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="thread_interrupt",
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 _("нет")
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}
<a href="https://olgram.readthedocs.io/ru/latest/options.html#thread_interrupt">Прерывать поток</a>: <b>{4}
</b>
""")).format(thread_turn, info_turn, antiflood_turn, enable_always_second_message, thread_interrupt)
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_statistic_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
chat_id: ty.Optional[int] = None):
if call:
await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert(
types.InlineKeyboardButton(text="<< Завершить редактирование",
types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text="Следующий текст",
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="next_text",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text="Сбросить текст",
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="reset_text",
chat=empty))
)
text = dedent("""
Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту {0}
команду /start
text = dedent(_("""
Статистика по боту @{0}
Текущий текст:
<pre>
{1}
</pre>
Отправьте сообщение, чтобы изменить текст.
""")
text = text.format(bot.name, bot.start_text)
Входящих сообщений: <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:
@@ -175,36 +315,101 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] =
async def send_bot_second_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
chat_id: ty.Optional[int] = 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="<< Завершить редактирование",
types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text="Предыдущий текст",
types.InlineKeyboardButton(text=_("Предыдущий текст"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text="Сбросить текст",
types.InlineKeyboardButton(text=_("Шаблоны ответов..."),
callback_data=menu_callback.new(level=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("""
text = dedent(_("""
Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в ответ на все входящие сообщения @{0} \
автоматически. По умолчанию оно отключено.
Текущий текст:
Текущий текст{2}:
<pre>{1}</pre>
Отправьте сообщение, чтобы изменить текст.
"""))
text = text.format(bot.name,
prepared_languages.get(lang, bot.second_text or _("отключено")),
_(" (для языка {0})").format(languages[lang]) if lang != "none" else "")
if call:
await edit_or_create(call, text, keyboard, parse_mode="HTML")
else:
await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML")
async def send_bot_templates_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
chat_id: ty.Optional[int] = None):
if call:
await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert(
types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
)
text = dedent(_("""
Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:
<pre>
{1}
</pre>
Отправьте сообщение, чтобы изменить текст.
""")
text = text.format(bot.name, bot.second_text if bot.second_text else "(отключено)")
Отправьте какую-нибудь фразу (например: "Ваш заказ готов, ожидайте!"), чтобы добавить её в шаблон.
Чтобы удалить шаблон из списка, отправьте его номер в списке (например, 4)
"""))
templates = await bot.answers
total_text_len = sum(len(t.text) for t in templates) + len(text) # примерная длина текста
max_len = 1000
if total_text_len > 4000:
max_len = 100
templates_text = "\n".join(f"{n}. {wrap(template.text, max_len)}" for n, template in enumerate(templates))
if not templates_text:
templates_text = _("(нет шаблонов)")
text = text.format(bot.name, templates_text)
if call:
await edit_or_create(call, text, keyboard, parse_mode="HTML")
else:
@@ -215,20 +420,76 @@ async def send_bot_second_text_menu(bot: Bot, call: ty.Optional[types.CallbackQu
async def start_text_received(message: types.Message, state: FSMContext):
async with state.proxy() as proxy:
bot_id = proxy.get("bot_id")
lang = proxy.get("lang", "none")
bot = await Bot.get_or_none(pk=bot_id)
bot.start_text = message.text
await bot.save()
await send_bot_text_menu(bot, chat_id=message.chat.id)
if lang == "none":
bot.start_text = message.html_text
await bot.save(update_fields=["start_text"])
else:
obj, created = await BotStartMessage.get_or_create(bot=bot,
locale=lang,
defaults={"text": message.html_text})
if not created:
obj.text = message.html_text
await obj.save(update_fields=["text"])
await send_bot_text_menu(bot, chat_id=message.chat.id, state=state)
@dp.message_handler(state="wait_second_text", content_types="text", regexp="^[^/].+") # Not command
async def second_text_received(message: types.Message, state: FSMContext):
async with state.proxy() as proxy:
bot_id = proxy.get("bot_id")
lang = proxy.get("lang", "none")
bot = await Bot.get_or_none(pk=bot_id)
bot.second_text = message.text
await bot.save()
await send_bot_second_text_menu(bot, chat_id=message.chat.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="*")
@@ -241,10 +502,11 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
bot_id = callback_data.get("bot_id")
bot = await Bot.get_or_none(id=bot_id)
if not bot or (await bot.owner).telegram_id != call.from_user.id:
await call.answer("У вас нет прав на этого бота", show_alert=True)
await call.answer(_("У вас нет прав на этого бота"), show_alert=True)
return
if level == "1":
await state.reset_state()
return await send_bot_menu(bot, call)
operation = callback_data.get("operation")
@@ -254,25 +516,64 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
return await send_chats_menu(bot, call)
if operation == "delete":
return await send_bot_delete_menu(bot, call)
if operation == "stat":
return await send_bot_statistic_menu(bot, call)
if operation == "settings":
return await send_bot_settings_menu(bot, call)
if operation == "text":
await state.set_state("wait_start_text")
async with state.proxy() as proxy:
proxy["bot_id"] = bot.id
return await send_bot_text_menu(bot, call)
return await send_bot_text_menu(bot, call, state=state)
if level == "3":
if operation == "delete_yes":
return await bot_actions.delete_bot(bot, call)
if operation == "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 == "thread_interrupt":
await bot_actions.thread_interrupt(bot, call)
return await send_bot_settings_menu(bot, call)
if operation == "olgram_text":
await bot_actions.olgram_text(bot, call)
return await send_bot_settings_menu(bot, call)
if operation == "reset_text":
await bot_actions.reset_bot_text(bot, call)
return await send_bot_text_menu(bot, call)
await bot_actions.reset_bot_text(bot, call, state)
return await send_bot_text_menu(bot, call, state=state)
if operation.startswith("slang_"):
async with state.proxy() as proxy:
lang = operation.replace("slang_", "")
if lang == "none" or lang in languages:
proxy["lang"] = lang
return await send_bot_text_menu(bot, call, state=state)
if operation == "next_text":
await state.set_state("wait_second_text")
async with state.proxy() as proxy:
proxy["bot_id"] = bot.id
return await send_bot_second_text_menu(bot, call)
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)
return await send_bot_second_text_menu(bot, call)
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
View 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(_("Промокод активирован! Спасибо 🙌"))

View File

@@ -7,6 +7,7 @@ from aiogram.dispatcher import FSMContext
from textwrap import dedent
from olgram.settings import OlgramSettings
from olgram.utils.permissions import public
from locales.locale import _
from olgram.router import dp
@@ -19,11 +20,10 @@ async def start(message: types.Message, state: FSMContext):
"""
await state.reset_state()
# TODO: locale
await message.answer(dedent("""
await message.answer(dedent(_("""
Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее \
<a href="https://olgram.readthedocs.io">читайте здесь</a>.
<a href="https://olgram.readthedocs.io">читайте здесь</a>. Следите за обновлениями \
<a href="https://t.me/civsoc_it">здесь</a>.
Используйте эти команды, чтобы управлять этим ботом:
@@ -31,7 +31,7 @@ async def start(message: types.Message, state: FSMContext):
/mybots - управление ботами
/help - помощь
"""), parse_mode="html")
""")), parse_mode="html", disable_web_page_preview=True)
@dp.message_handler(commands=["help"], state="*")
@@ -40,11 +40,11 @@ async def help(message: types.Message, state: FSMContext):
"""
Команда /help
"""
await message.answer(dedent(f"""
await message.answer(dedent(_("""
Читайте инструкции на нашем сайте https://olgram.readthedocs.io
Техническая поддержка: @civsocit_feedback_bot
Версия {OlgramSettings.version()}
"""))
Версия {0}
""")).format(OlgramSettings.version()))
@dp.message_handler(commands=["chatid"], state="*")

View File

@@ -1,7 +1,8 @@
"""Наши собственные миграции, которые нельзя описать на языке SQL и с которыми не справится TortoiseORM/Aerich"""
import aioredis
from tortoise import transactions, Tortoise
from olgram.settings import TORTOISE_ORM
from olgram.settings import TORTOISE_ORM, ServerSettings
from olgram.models.models import MetaInfo, Bot
import logging
@@ -22,8 +23,51 @@ async def upgrade_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]
_migrations = [upgrade_1, upgrade_2, upgrade_3]
async def migrate():

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View File

@@ -0,0 +1,4 @@
-- upgrade --
ALTER TABLE "defaultanswer" ADD "text" TEXT NOT NULL;
-- downgrade --
ALTER TABLE "defaultanswer" DROP COLUMN "text";

View 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";

View File

@@ -3,6 +3,7 @@ from tortoise import fields
from uuid import uuid4
from textwrap import dedent
from olgram.settings import DatabaseSettings
from locales.locale import _
class MetaInfo(Model):
@@ -26,10 +27,10 @@ class Bot(Model):
owner = fields.ForeignKeyField("models.User", related_name="bots")
name = fields.CharField(max_length=33)
code = fields.UUIDField(default=uuid4, index=True)
start_text = fields.TextField(default=dedent("""
start_text = fields.TextField(default=dedent(_("""
Здравствуйте!
Напишите ваш вопрос и мы ответим вам в ближайшее время.
"""))
""")))
second_text = fields.TextField(null=True, default=None)
group_chats = fields.ManyToManyField("models.GroupChat", related_name="bots", on_delete=fields.relational.CASCADE,
@@ -38,6 +39,16 @@ class Bot(Model):
on_delete=fields.relational.CASCADE,
null=True)
incoming_messages_count = fields.BigIntField(default=0)
outgoing_messages_count = fields.BigIntField(default=0)
enable_threads = fields.BooleanField(default=False)
enable_additional_info = fields.BooleanField(default=False)
enable_olgram_text = fields.BooleanField(default=True)
enable_antiflood = fields.BooleanField(default=False)
enable_always_second_message = fields.BooleanField(default=False)
enable_thread_interrupt = fields.BooleanField(default=True)
def decrypted_token(self):
cryptor = DatabaseSettings.cryptor()
return cryptor.decrypt(self.token)
@@ -53,14 +64,44 @@ class Bot(Model):
return group_chat.chat_id
return (await self.owner).telegram_id
async def is_promo(self):
await self.fetch_related("owner")
return await self.owner.is_promo()
class Meta:
table = 'bot'
class BotStartMessage(Model):
id = fields.IntField(pk=True)
bot = fields.ForeignKeyField("models.Bot", related_name="start_texts", on_delete=fields.CASCADE)
locale = fields.CharField(max_length=5)
text = fields.TextField()
class Meta:
unique_together = ("bot", "locale")
table = 'bot_start_message'
class BotSecondMessage(Model):
id = fields.IntField(pk=True)
bot = fields.ForeignKeyField("models.Bot", related_name="second_texts", on_delete=fields.CASCADE)
locale = fields.CharField(max_length=5)
text = fields.TextField()
class Meta:
unique_together = ("bot", "locale")
table = 'bot_second_message'
class User(Model):
id = fields.IntField(pk=True)
telegram_id = fields.BigIntField(index=True, unique=True)
async def is_promo(self):
await self.fetch_related("promo")
return bool(self.promo)
class Meta:
table = 'user'
@@ -72,3 +113,29 @@ class GroupChat(Model):
class Meta:
table = 'group_chat'
class BannedUser(Model):
id = fields.BigIntField(pk=True)
telegram_id = fields.BigIntField(index=True)
username = fields.CharField(max_length=100, default=None, null=True)
bot = fields.ForeignKeyField("models.Bot", related_name="banned_users", on_delete=fields.relational.CASCADE)
class Meta:
table = "bot_banned_user"
class DefaultAnswer(Model):
id = fields.BigIntField(pk=True)
bot = fields.ForeignKeyField("models.Bot", related_name="answers", on_delete=fields.relational.CASCADE)
text = fields.TextField()
class Promo(Model):
id = fields.BigIntField(pk=True)
code = fields.UUIDField(default=uuid4, index=True)
date = fields.DatetimeField(auto_now_add=True)
owner = fields.ForeignKeyField("models.User", related_name="promo", on_delete=fields.relational.SET_NULL,
null=True, default=None)

View File

@@ -1,8 +1,11 @@
from dotenv import load_dotenv
from abc import ABC
import os
from olgram.utils.crypto import Cryptor
import logging
from functools import lru_cache
from datetime import timedelta
import typing as ty
from olgram.utils.crypto import Cryptor
load_dotenv()
@@ -28,14 +31,29 @@ class OlgramSettings(AbstractSettings):
"""
return 10
@classmethod
def max_bots_per_user_promo(cls) -> int:
"""
Максимальное количество ботов у одного пользователя с промо-доступом
:return: int
"""
return 25
@classmethod
def version(cls):
return "0.1.1"
return "0.7.1"
@classmethod
@lru_cache
def admin_id(cls):
return cls._get_env("ADMIN_ID", True)
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):
@@ -47,10 +65,6 @@ class ServerSettings(AbstractSettings):
def hook_port(cls) -> int:
return int(cls._get_env("WEBHOOK_PORT"))
@classmethod
def app_host(cls) -> str:
return "olgram"
@classmethod
def app_port(cls) -> int:
return 80
@@ -80,9 +94,24 @@ class ServerSettings(AbstractSettings):
def append_text(cls) -> str:
return "\n\nЭтот бот создан с помощью @OlgramBot"
@classmethod
@lru_cache
def redis_timeout_ms(cls) -> ty.Optional[int]:
return int(timedelta(days=180).total_seconds() * 1000.0)
@classmethod
@lru_cache
def thread_timeout_ms(cls) -> int:
return int(timedelta(days=1).total_seconds() * 1000.0)
logging.basicConfig(level=os.environ.get("LOGLEVEL") or "WARNING",
format='%(asctime)s %(levelname)-8s %(message)s')
class BotSettings(AbstractSettings):
@classmethod
@lru_cache
def token(cls) -> str:
"""
Токен olgram бота
@@ -90,6 +119,14 @@ class BotSettings(AbstractSettings):
"""
return cls._get_env("BOT_TOKEN")
@classmethod
def language(cls) -> str:
"""
Язык
"""
lang = cls._get_env("O_LANG", allow_none=True)
return lang.lower() if lang else "ru"
class DatabaseSettings(AbstractSettings):
@classmethod

View File

@@ -22,8 +22,11 @@ async def edit_or_create(call: CallbackQuery, message: str,
parse_mode=parse_mode)
def button_text_limit(data: str) -> str:
max_len = 30
def wrap(data: str, max_len: int) -> str:
if len(data) > max_len:
data = data[:max_len-4] + "..."
return data
def button_text_limit(data: str) -> str:
return wrap(data, 30)

View File

@@ -1,6 +1,8 @@
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():
@@ -18,8 +20,8 @@ def public():
class AccessMiddleware(BaseMiddleware):
def __init__(self, access_chat_id: int):
self._access_chat_id = access_chat_id
def __init__(self, access_chat_ids: ty.Iterable[int]):
self._access_chat_ids = access_chat_ids
super(AccessMiddleware, self).__init__()
@classmethod
@@ -28,25 +30,25 @@ class AccessMiddleware(BaseMiddleware):
return handler and getattr(handler, "access_public", False)
async def on_process_message(self, message: types.Message, data: dict):
admin_id = self._access_chat_id
if not admin_id:
return # Администратор бота вообще не указан
admin_ids = self._access_chat_ids
if not admin_ids:
return # Администраторы бота вообще не указаны
if self._is_public_command(): # Эта команда разрешена всем пользователям
return
if message.chat.id != admin_id:
await message.answer("Владелец бота ограничил доступ к этому функционалу 😞")
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_id = self._access_chat_id
if not admin_id:
return # Администратор бота вообще не указан
admin_ids = self._access_chat_ids
if not admin_ids:
return # Администраторы бота вообще не указаны
if self._is_public_command(): # Эта команда разрешена всем пользователям
return
if call.message.chat.id != admin_id:
await call.answer("Владелец бота ограничил доступ к этому функционалу😞")
if call.message.chat.id not in admin_ids:
await call.answer(_("Владелец бота ограничил доступ к этому функционалу😞"))
raise CancelHandler()

1082
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,25 @@
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
[project]
[tool.poetry]
name = "olgram"
authors = [{name = "civsocit", email = "feedback@civsoc.it"}]
dynamic = ["version", "description"]
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
View File

@@ -0,0 +1 @@
/usr/lib/python3.11/Tools/i18n/pygettext.py -d chinese -o locales/olgram.pot olgram/ server/

View File

@@ -1,8 +0,0 @@
aiogram~=2.13
tortoise-orm[asyncpg]
aerich==0.5.4
python-dotenv~=0.17.1
aioredis==1.3.1
aiocache
aiohttp
pycrypto

View File

@@ -7,20 +7,28 @@ from contextvars import ContextVar
from aiohttp.web_exceptions import HTTPNotFound
from aioredis.commands import create_redis_pool
from aioredis import Redis
from tortoise.expressions import F
import logging
import typing as ty
from olgram.settings import ServerSettings
from olgram.models.models import Bot, GroupChat
from olgram.models.models import Bot, GroupChat, BannedUser, BotStartMessage, BotSecondMessage
from locales.locale import _, translators
from server.inlines import inline_handler
logging.basicConfig(level=logging.INFO)
_logger = logging.getLogger(__name__)
_logger.setLevel(logging.INFO)
db_bot_instance: ContextVar[Bot] = ContextVar('db_bot_instance')
_redis: ty.Optional[Redis] = None
def _get_translator(message: types.Message) -> ty.Callable:
if not message.from_user.locale:
return _
return translators.get(message.from_user.locale.language, _)
async def init_redis():
global _redis
_redis = await create_redis_pool(ServerSettings.redis_path())
@@ -30,49 +38,217 @@ def _message_unique_id(bot_id: int, message_id: int) -> str:
return f"{bot_id}_{message_id}"
async def message_handler(message, *args, **kwargs):
_logger.info("message handler")
def _thread_uniqie_id(bot_id: int, chat_id: int) -> str:
return f"thread_{bot_id}_{chat_id}"
def _last_message_uid(bot_id: int, chat_id: int) -> str:
return f"lm_{bot_id}_{chat_id}"
def _antiflood_marker_uid(bot_id: int, chat_id: int) -> str:
return f"af_{bot_id}_{chat_id}"
def _on_security_policy(message: types.Message, bot):
_ = _get_translator(message)
text = _("<b>Политика конфиденциальности</b>\n\n"
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При отправке сообщения (кроме команд "
"/start и /security_policy) ваш идентификатор пользователя записывается в кеш на некоторое время и потом "
"удаляется из кеша. Этот идентификатор используется только для общения с оператором; боты Olgram "
"не делают массовых рассылок.\n\n")
if bot.enable_additional_info:
text += _("При отправке сообщения (кроме команд /start и /security_policy) оператор <b>видит</b> ваши имя "
"пользователя, @username и идентификатор пользователя в силу настроек, которые оператор указал при "
"создании бота.")
else:
text += _("В зависимости от ваших настроек конфиденциальности Telegram, оператор может видеть ваш username, "
"имя пользователя и другую информацию.")
return SendMessage(chat_id=message.chat.id,
text=text,
parse_mode="HTML")
async def send_user_message(message: types.Message, super_chat_id: int, bot):
"""Переслать сообщение от пользователя, добавлять к нему user info при необходимости"""
if bot.enable_additional_info:
user_info = _("Сообщение от пользователя ")
user_info += message.from_user.full_name
if message.from_user.username:
user_info += " | @" + message.from_user.username
user_info += f" | #ID{message.from_user.id}"
if message.from_user.locale:
user_info += f" | lang: {message.from_user.locale}"
if message.forward_sender_name:
user_info += f" | fwd: {message.forward_sender_name}"
# Добавлять информацию в конец текста
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())
else:
try:
new_message = await message.forward(super_chat_id)
except exceptions.MessageCantBeForwarded:
new_message = await message.copy_to(super_chat_id)
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id,
pexpire=ServerSettings.redis_timeout_ms())
return new_message
async def send_to_superchat(is_super_group: bool, message: types.Message, super_chat_id: int, bot):
"""Пересылка сообщения от пользователя оператору (логика потоков сообщений)"""
if is_super_group and bot.enable_threads:
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_uniqie_id(bot.pk, message.chat.id))
if thread_first_message:
# переслать в супер-чат, отвечая на предыдущее сообщение
try:
new_message = await message.copy_to(super_chat_id, reply_to_message_id=int(thread_first_message))
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id,
pexpire=thread_timeout)
except exceptions.BadRequest:
new_message = await send_user_message(message, super_chat_id, bot)
await _redis.set(
_thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id, pexpire=thread_timeout)
else:
# переслать супер-чат
new_message = await send_user_message(message, super_chat_id, bot)
await _redis.set(_thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id,
pexpire=thread_timeout)
else: # личные сообщения не поддерживают потоки сообщений: просто отправляем сообщение
await send_user_message(message, super_chat_id, bot)
async def handle_user_message(message: types.Message, super_chat_id: int, bot):
"""Обычный пользователь прислал сообщение в бот, нужно переслать его операторам"""
_ = _get_translator(message)
is_super_group = super_chat_id < 0
# Проверить, не забанен ли пользователь
banned = await bot.banned_users.filter(telegram_id=message.chat.id)
if banned:
return SendMessage(chat_id=message.chat.id,
text=_("Вы заблокированы в этом боте"))
# Проверить анти-флуд
if bot.enable_antiflood:
if await _redis.get(_antiflood_marker_uid(bot.pk, message.chat.id)):
return SendMessage(chat_id=message.chat.id,
text=_("Слишком много сообщений, подождите одну минуту"))
await _redis.setex(_antiflood_marker_uid(bot.pk, message.chat.id), 60, 1)
# Пересылаем сообщение в супер-чат
try:
await send_to_superchat(is_super_group, message, super_chat_id, bot)
except (exceptions.Unauthorized, exceptions.ChatNotFound):
return SendMessage(chat_id=message.chat.id, text=_("Не удаётся связаться с владельцем бота"))
except exceptions.TelegramAPIError as err:
_logger.error(f"(exception on forwarding) {err}")
return
bot.incoming_messages_count = F("incoming_messages_count") + 1
await bot.save(update_fields=["incoming_messages_count"])
# И отправить пользователю специальный текст, если он указан и если давно не отправляли
if bot.second_text:
send_auto = not await _redis.get(_last_message_uid(bot.pk, message.chat.id))
await _redis.setex(_last_message_uid(bot.pk, message.chat.id), 60 * 60 * 3, 1)
if send_auto 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=_("Пользователь разбанен"))
try:
await message.copy_to(chat_id)
except (exceptions.MessageError, exceptions.Unauthorized):
await message.reply(_("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"),
parse_mode="HTML")
return
bot.outgoing_messages_count = F("outgoing_messages_count") + 1
await bot.save(update_fields=["outgoing_messages_count"])
elif super_chat_id > 0:
# в супер-чате кто-то пишет сообщение сам себе, только для личных сообщений
await message.forward(super_chat_id)
# И отправить пользователю специальный текст, если он указан
if bot.second_text:
return SendMessage(chat_id=message.chat.id, text=bot.second_text, parse_mode="HTML")
async def message_handler(message: types.Message, *args, **kwargs):
_ = _get_translator(message)
bot = db_bot_instance.get()
if message.text and message.text.startswith("/start"):
if message.text and message.text == "/start":
# На команду start нужно ответить, не пересылая сообщение никуда
return SendMessage(chat_id=message.chat.id,
text=bot.start_text + ServerSettings.append_text())
text_obj = await BotStartMessage.get_or_none(bot=bot, locale=str(message.from_user.locale))
text = text_obj.text if text_obj else bot.start_text
if bot.enable_olgram_text:
text += _(ServerSettings.append_text())
return SendMessage(chat_id=message.chat.id, text=text, parse_mode="HTML")
if message.text and message.text == "/security_policy":
# На команду security_policy нужно ответить, не пересылая сообщение никуда
return _on_security_policy(message, bot)
super_chat_id = await bot.super_chat_id()
if message.chat.id != super_chat_id:
# Это обычный чат: сообщение нужно переслать в супер-чат
new_message = await message.forward(super_chat_id)
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id)
# И отправить пользователю специальный текст, если он указан
if bot.second_text:
return SendMessage(chat_id=message.chat.id, text=bot.second_text)
# Это обычный чат
return await handle_user_message(message, super_chat_id, bot)
else:
# Это супер-чат
if message.reply_to_message:
# В супер-чате кто-то ответил на сообщение пользователя, нужно переслать тому пользователю
chat_id = await _redis.get(_message_unique_id(bot.pk, message.reply_to_message.message_id))
if not chat_id:
chat_id = message.reply_to_message.forward_from_chat
if not chat_id:
return SendMessage(chat_id=message.chat.id,
text="<i>Невозможно переслать сообщение: автор не найден</i>",
parse_mode="HTML")
chat_id = int(chat_id)
try:
await message.copy_to(chat_id)
except (exceptions.MessageError, exceptions.BotBlocked):
await message.reply("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>",
parse_mode="HTML")
return
else:
# в супер-чате кто-то пишет сообщение сам себе
await message.forward(super_chat_id)
# И отправить пользователю специальный текст, если он указан
if bot.second_text:
return SendMessage(chat_id=message.chat.id, text=bot.second_text)
return await handle_operator_message(message, super_chat_id, bot)
async def edited_message_handler(message: types.Message, *args, **kwargs):
return await message_handler(message, *args, **kwargs, is_edited=True)
async def receive_invite(message: types.Message):
@@ -89,6 +265,18 @@ async def receive_invite(message: types.Message):
break
async def receive_group_create(message: types.Message):
bot = db_bot_instance.get()
chat, _ = await GroupChat.get_or_create(chat_id=message.chat.id,
defaults={"name": message.chat.full_name})
chat.name = message.chat.full_name
await chat.save()
if chat not in await bot.group_chats.all():
await bot.group_chats.add(chat)
await bot.save()
async def receive_left(message: types.Message):
bot = db_bot_instance.get()
if message.left_chat_member.id == message.bot.id:
@@ -101,6 +289,23 @@ async def receive_left(message: types.Message):
await bot.save()
async def receive_inline(inline_query):
_logger.info("inline handler")
bot = db_bot_instance.get()
return await inline_handler(inline_query, bot)
async def receive_migrate(message: types.Message):
bot = db_bot_instance.get()
from_id = message.chat.id
to_id = message.migrate_to_chat_id
chats = await bot.group_chats.filter(chat_id=from_id)
for chat in chats:
chat.chat_id = to_id
await chat.save(update_fields=["chat_id"])
class CustomRequestHandler(WebhookRequestHandler):
def __init__(self, *args, **kwargs):
@@ -116,17 +321,24 @@ class CustomRequestHandler(WebhookRequestHandler):
db_bot_instance.set(bot)
dp = Dispatcher(AioBot(bot.decrypted_token()))
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])
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(receive_invite, content_types=[types.ContentType.NEW_CHAT_MEMBERS])
dp.register_message_handler(receive_left, content_types=[types.ContentType.LEFT_CHAT_MEMBER])
dp.register_message_handler(receive_migrate, content_types=[types.ContentType.MIGRATE_TO_CHAT_ID])
dp.register_message_handler(receive_group_create, content_types=[types.ContentType.GROUP_CHAT_CREATED])
dp.register_inline_handler(receive_inline)
return dp

56
server/inlines.py Normal file
View 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)

View File

@@ -1,9 +1,11 @@
from aiogram import Bot as AioBot
from aiogram.types import BotCommand
from olgram.models.models import Bot
from aiohttp import web
from asyncio import get_event_loop
import ssl
from olgram.settings import ServerSettings
from locales.locale import _
from .custom import CustomRequestHandler
import logging
@@ -33,7 +35,13 @@ async def register_token(bot: Bot) -> bool:
if ServerSettings.use_custom_cert():
certificate = open(ServerSettings.public_path(), 'rb')
res = await a_bot.set_webhook(url_for_bot(bot), certificate=certificate, drop_pending_updates=True)
res = await a_bot.set_webhook(url_for_bot(bot), certificate=certificate, drop_pending_updates=True,
max_connections=10)
await a_bot.set_my_commands([
BotCommand("/start", _("(Пере)запустить бота")),
BotCommand("/security_policy", _("Политика конфиденциальности"))
])
await a_bot.session.close()
del a_bot
return res
@@ -65,5 +73,5 @@ def main():
runner = web.AppRunner(app)
loop.run_until_complete(runner.setup())
logger.info("Server initialization done")
site = web.TCPSite(runner, host=ServerSettings.app_host(), port=ServerSettings.app_port(), ssl_context=context)
site = web.TCPSite(runner, port=ServerSettings.app_port(), ssl_context=context)
return site