Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a178183251 | |||
| 4194f3da48 | |||
| 4a947cec3d | |||
| b28b7c583d |
@@ -1,6 +1,6 @@
|
|||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y libolm3 libolm-dev git && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install --no-install-recommends -y libmagic1 libolm3 libolm-dev git && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -6,24 +6,24 @@
|
|||||||
|
|
||||||
## Для запуска рекомендуется `docker`
|
## Для запуска рекомендуется `docker`
|
||||||
|
|
||||||
1. Соберите образ:
|
1. [Скачайте образ](https://git.tyukalov.su/nikita/-/packages/container/2026-matrix-csonac) (измените версию на нужную вам):
|
||||||
```bash
|
```bash
|
||||||
sudo docker build -t csonac .
|
sudo docker pull git.tyukalov.su/nikita/2026-matrix-csonac:0.4
|
||||||
```
|
```
|
||||||
2. Выполните первый запуск в интерактивном режиме
|
2. Выполните первый запуск в интерактивном режиме
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir runtime
|
sudo mkdir runtime
|
||||||
sudo chown 1000:1000 runtime
|
sudo chown 1000:1000 runtime
|
||||||
sudo docker run -v ./runtime:/runtime -ti csonac:latest
|
sudo docker run -v ./runtime:/runtime -ti git.tyukalov.su/nikita/2026-matrix-csonac:0.4
|
||||||
```
|
```
|
||||||
3. В папке `runtime` появится файл `config.json`. Внесите туда нужные настройки
|
3. В папке `runtime` появится файл `config.json`. Внесите туда нужные настройки
|
||||||
4. Запустите ещё раз в интерактивном режиме, чтобы авторизоваться
|
4. Запустите ещё раз в интерактивном режиме, чтобы авторизоваться
|
||||||
```bash
|
```bash
|
||||||
sudo docker run -v ./runtime:/runtime -ti csonac:latest
|
sudo docker run -v ./runtime:/runtime -ti git.tyukalov.su/nikita/2026-matrix-csonac:0.4
|
||||||
```
|
```
|
||||||
5. Дальше можно запускать контейнер в фоне
|
5. Дальше можно запускать контейнер в фоне
|
||||||
```bash
|
```bash
|
||||||
sudo docker run -p 127.0.0.1:4980:4980 -v ./runtime:/runtime --detach csonac:latest
|
sudo docker run -p 127.0.0.1:4980:4980 -v ./runtime:/runtime --detach git.tyukalov.su/nikita/2026-matrix-csonac:0.4
|
||||||
```
|
```
|
||||||
> Рекомендуется запускать контейнер при помощи `docker compose`.
|
> Рекомендуется запускать контейнер при помощи `docker compose`.
|
||||||
|
|
||||||
@@ -66,6 +66,7 @@ python main.py
|
|||||||
- `!ban <IP> <SECONDS> [REASON]` - забанить указанный IP на указанное число секунд (можно указать причину)
|
- `!ban <IP> <SECONDS> [REASON]` - забанить указанный IP на указанное число секунд (можно указать причину)
|
||||||
- `!unban <IP>` - разбанить указанный IP адрес
|
- `!unban <IP>` - разбанить указанный IP адрес
|
||||||
- `!bans` - получить список забаненных IP адресов
|
- `!bans` - получить список забаненных IP адресов
|
||||||
|
- `!short <TOKEN>` - создать токен с указанным именем
|
||||||
|
|
||||||
## Как работает веб-сервер
|
## Как работает веб-сервер
|
||||||
|
|
||||||
|
|||||||
226
bot_callbacks.py
226
bot_callbacks.py
@@ -4,8 +4,7 @@ import html
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from mab import MatrixBot
|
from mab import * # type: ignore
|
||||||
from nio import MatrixRoom, RoomMessageText
|
|
||||||
|
|
||||||
import util
|
import util
|
||||||
from database import Database
|
from database import Database
|
||||||
@@ -14,9 +13,21 @@ from datatypes import *
|
|||||||
#
|
#
|
||||||
# PRIVATE
|
# PRIVATE
|
||||||
#
|
#
|
||||||
_bot: MatrixBot = None # type: ignore
|
|
||||||
_db: Database = None # type: ignore
|
_db: Database = None # type: ignore
|
||||||
|
|
||||||
|
_COMMANDS = {
|
||||||
|
"help": "Получить справку",
|
||||||
|
"info": "Получить сведения о комнате",
|
||||||
|
"tokens": "Получить список токенов",
|
||||||
|
"auth": "Создать новый токен",
|
||||||
|
"deauth": "Удалить существующий токен",
|
||||||
|
"name": "Задать (или удалить) имя для токена",
|
||||||
|
"ban": "Забанить IP адрес",
|
||||||
|
"unban": "Разбанить IP адрес",
|
||||||
|
"bans": "Получить список забаненных IP адресов",
|
||||||
|
"short": "(Небезопасно) Создать токен-имя",
|
||||||
|
}
|
||||||
|
|
||||||
def _generate_help_message() -> str:
|
def _generate_help_message() -> str:
|
||||||
"""Generates help message in HTML markup"""
|
"""Generates help message in HTML markup"""
|
||||||
result = "<strong><i>Как использовать</i></strong><br><ol>"
|
result = "<strong><i>Как использовать</i></strong><br><ol>"
|
||||||
@@ -27,75 +38,42 @@ def _generate_help_message() -> str:
|
|||||||
result += "</ol>"
|
result += "</ol>"
|
||||||
|
|
||||||
result += "<br><br><strong><i>Команды</i></strong>"
|
result += "<br><br><strong><i>Команды</i></strong>"
|
||||||
for aliases in _COMMANDS:
|
for key in _COMMANDS:
|
||||||
result += f"<br><code>!{aliases[0]}</code> - <i>{html.escape(_COMMANDS[aliases][1])}</i>"
|
result += f"<br><code>!{key}</code> - <i>{html.escape(_COMMANDS[key])}</i>"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
#
|
|
||||||
# GENERIC EVENT HANDLERS
|
|
||||||
#
|
|
||||||
async def _on_text(room: MatrixRoom, event: RoomMessageText) -> None:
|
|
||||||
"""This callback is called when text message is received"""
|
|
||||||
if event.sender == _bot.get_client().user_id:
|
|
||||||
return
|
|
||||||
text = event.body
|
|
||||||
parts = [p for p in text.split() if p.strip()]
|
|
||||||
if len(parts) < 1:
|
|
||||||
return
|
|
||||||
cmd = parts[0].lower()
|
|
||||||
if not cmd.startswith("!"):
|
|
||||||
return
|
|
||||||
cb = None
|
|
||||||
for aliases in _COMMANDS:
|
|
||||||
for alias in aliases:
|
|
||||||
if alias == cmd.lstrip("!"):
|
|
||||||
cb = _COMMANDS[aliases][0]
|
|
||||||
break
|
|
||||||
if cb:
|
|
||||||
break
|
|
||||||
if cb is None:
|
|
||||||
await _bot.send_text_to_room(room.room_id, "<strong>Используйте <code>!help</code></strong>")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await cb(room, parts[1:])
|
|
||||||
except:
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# COMMAND HANDLERS
|
# COMMAND HANDLERS
|
||||||
#
|
#
|
||||||
async def _on_cmd_help(room: MatrixRoom, args: list[str]) -> None:
|
async def _on_cmd_help(ctx: EventContext) -> None:
|
||||||
"""!help"""
|
"""!help"""
|
||||||
await _bot.send_text_to_room(room.room_id, _generate_help_message(), is_html=True)
|
await ctx.bot.send_text(ctx.room, _generate_help_message())
|
||||||
|
|
||||||
async def _on_cmd_info(room: MatrixRoom, args: list[str]) -> None:
|
async def _on_cmd_info(ctx: EventContext) -> None:
|
||||||
"""!info"""
|
"""!info"""
|
||||||
# get room info (or add it)
|
# get room info (or add it)
|
||||||
room_info = await _db.room_get(room.room_id)
|
room_info = await _db.room_get(ctx.room.room_id)
|
||||||
if room_info is None:
|
if room_info is None:
|
||||||
room_info = await _db.room_create(room.room_id)
|
room_info = await _db.room_create(ctx.room.room_id)
|
||||||
# failure
|
# failure
|
||||||
if room_info is None:
|
if room_info is None:
|
||||||
await _bot.send_text_to_room(
|
await ctx.bot.send_text(
|
||||||
room.room_id,
|
ctx.room, "<strong>Нет информации о комнате</strong>"
|
||||||
"<strong>Нет информации о комнате</strong>",
|
|
||||||
is_html=True
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
# respond
|
# respond
|
||||||
response = "<strong><i>Сведения о комнате</i></strong><br>"
|
response = "<strong><i>Сведения о комнате</i></strong><br>"
|
||||||
response += f"<strong>Канал:</strong> <code>{html.escape(room_info.code)}</code>"
|
response += f"<strong>Канал:</strong> <code>{html.escape(room_info.code)}</code>"
|
||||||
await _bot.send_text_to_room(room.room_id, response, is_html=True)
|
await ctx.bot.send_text(ctx.room, response)
|
||||||
|
|
||||||
async def _on_cmd_tokens(room: MatrixRoom, args: list[str]) -> None:
|
async def _on_cmd_tokens(ctx: EventContext) -> None:
|
||||||
"""!info"""
|
"""!info"""
|
||||||
# get all tokens and check if there are none
|
# get all tokens and check if there are none
|
||||||
tokens = await _db.token_get_all()
|
tokens = await _db.token_get_all()
|
||||||
if not tokens:
|
if not tokens:
|
||||||
await _bot.send_text_to_room(
|
await ctx.bot.send_text(
|
||||||
room.room_id,
|
ctx.room,
|
||||||
"<strong>Нет токенов, используйте <code>!auth</code></strong>",
|
"<strong>Нет токенов, используйте <code>!auth</code></strong>"
|
||||||
is_html=True
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
# create the list of tokens
|
# create the list of tokens
|
||||||
@@ -110,15 +88,15 @@ async def _on_cmd_tokens(room: MatrixRoom, args: list[str]) -> None:
|
|||||||
response += f"<li><strong>Последнее использование:</strong> <i>{util.date_to_text(token.last_access_at)}</i></li>"
|
response += f"<li><strong>Последнее использование:</strong> <i>{util.date_to_text(token.last_access_at)}</i></li>"
|
||||||
response += "</ul>"
|
response += "</ul>"
|
||||||
# respond
|
# respond
|
||||||
await _bot.send_text_to_room(room.room_id, response, is_html=True)
|
await ctx.bot.send_text(ctx.room, response)
|
||||||
|
|
||||||
async def _on_cmd_auth(room: MatrixRoom, args: list[str]) -> None:
|
async def _on_cmd_auth(ctx: EventContext) -> None:
|
||||||
"""!auth"""
|
"""!auth"""
|
||||||
# create new token
|
# create new token
|
||||||
token = await _db.token_create()
|
token = await _db.token_create()
|
||||||
# set the name if it is provided
|
# set the name if it is provided
|
||||||
if args:
|
if ctx[CTX_CMD_ARGS]:
|
||||||
token.name = " ".join(args)
|
token.name = " ".join(ctx[CTX_CMD_ARGS])
|
||||||
await _db.token_set_name(token.code, token.name)
|
await _db.token_set_name(token.code, token.name)
|
||||||
# create the response
|
# create the response
|
||||||
response = "<strong>Создан новый токен</strong>"
|
response = "<strong>Создан новый токен</strong>"
|
||||||
@@ -129,39 +107,39 @@ async def _on_cmd_auth(room: MatrixRoom, args: list[str]) -> None:
|
|||||||
else:
|
else:
|
||||||
response += "указывается в запросе"
|
response += "указывается в запросе"
|
||||||
# respond
|
# respond
|
||||||
await _bot.send_text_to_room(room.room_id, response, is_html=True)
|
await ctx.bot.send_text(ctx.room, response)
|
||||||
|
|
||||||
async def _on_cmd_deauth(room: MatrixRoom, args: list[str]) -> None:
|
async def _on_cmd_deauth(ctx: EventContext) -> None:
|
||||||
"""!deauth"""
|
"""!deauth"""
|
||||||
# no token provided
|
# no token provided
|
||||||
if len(args) != 1:
|
if len(ctx[CTX_CMD_ARGS]) != 1:
|
||||||
await _bot.send_text_to_room(room.room_id, "<strong>Укажите токен, который надо удалить (должен быть ровно один аргумент)</strong>", is_html=True)
|
await ctx.bot.send_text(ctx.room, "<strong>Укажите токен, который надо удалить (должен быть ровно один аргумент)</strong>")
|
||||||
return
|
return
|
||||||
token = args[0]
|
token = ctx[CTX_CMD_ARGS][0]
|
||||||
# check if token does not exist
|
# check if token does not exist
|
||||||
if await _db.token_get(token) is None:
|
if await _db.token_get(token) is None:
|
||||||
await _bot.send_text_to_room(room.room_id, "<strong>Токен не найден</strong>", is_html=True)
|
await ctx.bot.send_text(ctx.room, "<strong>Токен не найден</strong>")
|
||||||
return
|
return
|
||||||
# remove the token
|
# remove the token
|
||||||
await _db.token_delete(token)
|
await _db.token_delete(token)
|
||||||
# respond
|
# respond
|
||||||
response = f"<strong>Удалён токен <code>{token}</code></strong>"
|
response = f"<strong>Удалён токен <code>{token}</code></strong>"
|
||||||
await _bot.send_text_to_room(room.room_id, response, is_html=True)
|
await ctx.bot.send_text(ctx.room, response)
|
||||||
|
|
||||||
async def _on_cmd_name(room: MatrixRoom, args: list[str]) -> None:
|
async def _on_cmd_name(ctx: EventContext) -> None:
|
||||||
"""!name"""
|
"""!name"""
|
||||||
# check arguments
|
# check arguments
|
||||||
if len(args) < 1:
|
if len(ctx[CTX_CMD_ARGS]) < 1:
|
||||||
error = "<strong>Требуется указать как минимум токен. Если хотите убрать имя, то имя указывать не надо. Если имя нужно назначить или изменить, то после токена укажите новое имя.</strong>"
|
error = "<strong>Требуется указать как минимум токен. Если хотите убрать имя, то имя указывать не надо. Если имя нужно назначить или изменить, то после токена укажите новое имя.</strong>"
|
||||||
await _bot.send_text_to_room(room.room_id, error, is_html=True)
|
await ctx.bot.send_text(ctx.room, error)
|
||||||
return
|
return
|
||||||
token = args[0]
|
token = ctx[CTX_CMD_ARGS][0]
|
||||||
new_name = " ".join(args[1:])
|
new_name = " ".join(ctx[CTX_CMD_ARGS][1:])
|
||||||
if not new_name.strip():
|
if not new_name.strip():
|
||||||
new_name = None
|
new_name = None
|
||||||
# check if token exists
|
# check if token exists
|
||||||
if await _db.token_get(token) is None:
|
if await _db.token_get(token) is None:
|
||||||
await _bot.send_text_to_room(room.room_id, "<strong>Токен не существует</strong>", is_html=True)
|
await ctx.bot.send_text(ctx.room, "<strong>Токен не существует</strong>")
|
||||||
return
|
return
|
||||||
# set new name
|
# set new name
|
||||||
await _db.token_set_name(token, new_name)
|
await _db.token_set_name(token, new_name)
|
||||||
@@ -171,51 +149,51 @@ async def _on_cmd_name(room: MatrixRoom, args: list[str]) -> None:
|
|||||||
else:
|
else:
|
||||||
response = f"<strong>Удалено имя для токена <code>{token}</code></strong>"
|
response = f"<strong>Удалено имя для токена <code>{token}</code></strong>"
|
||||||
# respond
|
# respond
|
||||||
await _bot.send_text_to_room(room.room_id, response, is_html=True)
|
await ctx.bot.send_text(ctx.room, response)
|
||||||
|
|
||||||
async def _on_cmd_ban(room: MatrixRoom, args: list[str]) -> None:
|
async def _on_cmd_ban(ctx: EventContext) -> None:
|
||||||
"""!ban"""
|
"""!ban"""
|
||||||
# check arguments
|
# check arguments
|
||||||
if len(args) < 2:
|
if len(ctx[CTX_CMD_ARGS]) < 2:
|
||||||
error = "<strong>Формат: <code>!ban <IP> <SECONDS> [REASON]</code></strong>"
|
error = "<strong>Формат: <code>!ban <IP> <SECONDS> [REASON]</code></strong>"
|
||||||
await _bot.send_text_to_room(room.room_id, error, is_html=True)
|
await ctx.bot.send_text(ctx.room, error)
|
||||||
return
|
return
|
||||||
# get args
|
# get event.command_args
|
||||||
try:
|
try:
|
||||||
ip = args[0]
|
ip = ctx[CTX_CMD_ARGS][0]
|
||||||
duration = float(args[1])
|
duration = float(ctx[CTX_CMD_ARGS][1])
|
||||||
reason = " ".join(args[2:]) if args[2:] else "Manual ban"
|
reason = " ".join(ctx[CTX_CMD_ARGS][2:]) if ctx[CTX_CMD_ARGS][2:] else "Manual ban"
|
||||||
except:
|
except:
|
||||||
error = "<strong>Возникла ошибка. Наверняка неправильно указаны секунды.</strong>"
|
error = "<strong>Возникла ошибка. Наверняка неправильно указаны секунды.</strong>"
|
||||||
await _bot.send_text_to_room(room.room_id, error, is_html=True)
|
await ctx.bot.send_text(ctx.room, error)
|
||||||
return
|
return
|
||||||
# ban
|
# ban
|
||||||
try:
|
try:
|
||||||
await _db.ban_create(ip, time.time() + duration, reason)
|
await _db.ban_create(ip, time.time() + duration, reason)
|
||||||
await _bot.send_text_to_room(room.room_id, "<strong>IP адрес заблокирован</strong>", is_html=True)
|
await ctx.bot.send_text(ctx.room, "<strong>IP адрес заблокирован</strong>")
|
||||||
except:
|
except:
|
||||||
await _bot.send_text_to_room(room.room_id, "<strong>Возникла ошибка</strong>", is_html=True)
|
await ctx.bot.send_text(ctx.room, "<strong>Возникла ошибка</strong>")
|
||||||
|
|
||||||
async def _on_cmd_unban(room: MatrixRoom, args: list[str]) -> None:
|
async def _on_cmd_unban(ctx: EventContext) -> None:
|
||||||
"""!unban"""
|
"""!unban"""
|
||||||
# check arguments
|
# check arguments
|
||||||
if len(args) != 1:
|
if len(ctx[CTX_CMD_ARGS]) != 1:
|
||||||
error = "<strong>Формат: <code>!ban <IP></code></strong>"
|
error = "<strong>Формат: <code>!ban <IP></code></strong>"
|
||||||
await _bot.send_text_to_room(room.room_id, error, is_html=True)
|
await ctx.bot.send_text(ctx.room, error)
|
||||||
return
|
return
|
||||||
# unban
|
# unban
|
||||||
try:
|
try:
|
||||||
await _db.ban_delete(args[0])
|
await _db.ban_delete(ctx[CTX_CMD_ARGS][0])
|
||||||
await _bot.send_text_to_room(room.room_id, "<strong>IP адрес разблокирован (если он был заблокирован)</strong>", is_html=True)
|
await ctx.bot.send_text(ctx.room, "<strong>IP адрес разблокирован (если он был заблокирован)</strong>")
|
||||||
except:
|
except:
|
||||||
await _bot.send_text_to_room(room.room_id, "<strong>Возникла ошибка</strong>", is_html=True)
|
await ctx.bot.send_text(ctx.room, "<strong>Возникла ошибка</strong>")
|
||||||
|
|
||||||
async def _on_cmd_bans(room: MatrixRoom, args: list[str]) -> None:
|
async def _on_cmd_bans(ctx: EventContext) -> None:
|
||||||
"""!bans"""
|
"""!bans"""
|
||||||
# list
|
# list
|
||||||
bans = _db.ban_get_all()
|
bans = _db.ban_get_all()
|
||||||
if not bans:
|
if not bans:
|
||||||
await _bot.send_text_to_room(room.room_id, "<strong>Нет заблокированных IP адресов</strong>", is_html=True)
|
await ctx.bot.send_text(ctx.room, "<strong>Нет заблокированных IP адресов</strong>")
|
||||||
return
|
return
|
||||||
# create the response
|
# create the response
|
||||||
result = "<strong>Список заблокированных IP</strong><br><ul>"
|
result = "<strong>Список заблокированных IP</strong><br><ul>"
|
||||||
@@ -225,27 +203,73 @@ async def _on_cmd_bans(room: MatrixRoom, args: list[str]) -> None:
|
|||||||
result += f"<li><strong>Причина:</strong> <code>{html.escape(ban["reason"])}</code></li>"
|
result += f"<li><strong>Причина:</strong> <code>{html.escape(ban["reason"])}</code></li>"
|
||||||
result += "</ul></li>"
|
result += "</ul></li>"
|
||||||
result += "</ul>"
|
result += "</ul>"
|
||||||
await _bot.send_text_to_room(room.room_id, result, is_html=True)
|
await ctx.bot.send_text(ctx.room, result)
|
||||||
|
|
||||||
|
async def _on_cmd_short(ctx: EventContext) -> None:
|
||||||
|
"""!auth"""
|
||||||
|
# get token to use
|
||||||
|
if len(ctx[CTX_CMD_ARGS]) != 1:
|
||||||
|
error = "<strong>Формат: <code>!short <token></code></strong>"
|
||||||
|
await ctx.bot.send_text(ctx.room, error)
|
||||||
|
return
|
||||||
|
# create new token
|
||||||
|
try:
|
||||||
|
token = await _db.token_create(ctx[CTX_CMD_ARGS][0])
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.bot.send_text(ctx.room, f"Не удалось создать токен: {" ".join(e.args)}")
|
||||||
|
return
|
||||||
|
# create the response
|
||||||
|
response = "<strong>Создан новый токен</strong>"
|
||||||
|
response += f"<br><strong>Код:</strong> <code>{token.code}</code>"
|
||||||
|
response += f"<br><strong>Имя сервиса:</strong> указывается в запросе"
|
||||||
|
# respond
|
||||||
|
await ctx.bot.send_text(ctx.room, response)
|
||||||
|
|
||||||
_COMMANDS = {
|
|
||||||
("help", "h", "?"): (_on_cmd_help, "Получить справку"),
|
|
||||||
("info", "room", "i", "r"): (_on_cmd_info, "Получить сведения о комнате"),
|
|
||||||
("tokens", "t"): (_on_cmd_tokens, "Получить список токенов"),
|
|
||||||
("auth", "create", "a", "c"): (_on_cmd_auth, "Создать новый токен"),
|
|
||||||
("deauth", "delete", "d"): (_on_cmd_deauth, "Удалить существующий токен"),
|
|
||||||
("name", "n"): (_on_cmd_name, "Задать (или удалить) имя для токена"),
|
|
||||||
("ban", "b"): (_on_cmd_ban, "Забанить IP адрес"),
|
|
||||||
("unban", "u"): (_on_cmd_unban, "Разбанить IP адрес"),
|
|
||||||
("bans", "l"): (_on_cmd_bans, "Получить список забаненных IP адресов"),
|
|
||||||
}
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# PUBLIC
|
# PUBLIC
|
||||||
#
|
#
|
||||||
def setup(bot: MatrixBot, db: Database) -> None:
|
def setup(bot: MatrixBot, db: Database) -> None:
|
||||||
"""Setup the callbacks"""
|
"""Setup the callbacks"""
|
||||||
global _bot, _db
|
global _db
|
||||||
_db = db
|
_db = db
|
||||||
_bot = bot
|
bot.add_callback(
|
||||||
_bot.add_event_callback(_on_text, RoomMessageText) # type: ignore
|
~SenderIsBotFilter() & BodyCommandFilter(["info", "room", "i", "r"]),
|
||||||
|
_on_cmd_info
|
||||||
|
)
|
||||||
|
bot.add_callback(
|
||||||
|
~SenderIsBotFilter() & BodyCommandFilter(["tokens", "t"]),
|
||||||
|
_on_cmd_tokens
|
||||||
|
)
|
||||||
|
bot.add_callback(
|
||||||
|
~SenderIsBotFilter() & BodyCommandFilter(["auth", "create", "a", "c"]),
|
||||||
|
_on_cmd_auth
|
||||||
|
)
|
||||||
|
bot.add_callback(
|
||||||
|
~SenderIsBotFilter() & BodyCommandFilter(["deauth", "delete", "d"]),
|
||||||
|
_on_cmd_deauth
|
||||||
|
)
|
||||||
|
bot.add_callback(
|
||||||
|
~SenderIsBotFilter() & BodyCommandFilter(["name", "n"]),
|
||||||
|
_on_cmd_name
|
||||||
|
)
|
||||||
|
bot.add_callback(
|
||||||
|
~SenderIsBotFilter() & BodyCommandFilter(["ban", "b"]),
|
||||||
|
_on_cmd_ban
|
||||||
|
)
|
||||||
|
bot.add_callback(
|
||||||
|
~SenderIsBotFilter() & BodyCommandFilter(["unban", "u"]),
|
||||||
|
_on_cmd_unban
|
||||||
|
)
|
||||||
|
bot.add_callback(
|
||||||
|
~SenderIsBotFilter() & BodyCommandFilter(["bans", "l"]),
|
||||||
|
_on_cmd_bans
|
||||||
|
)
|
||||||
|
bot.add_callback(
|
||||||
|
~SenderIsBotFilter() & BodyCommandFilter(["short", "s"]),
|
||||||
|
_on_cmd_short
|
||||||
|
)
|
||||||
|
bot.add_callback(
|
||||||
|
~SenderIsBotFilter() & NewMessageFilter(),
|
||||||
|
_on_cmd_help
|
||||||
|
)
|
||||||
21
database.py
21
database.py
@@ -252,11 +252,17 @@ class Database:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def token_create(self) -> ObjectToken:
|
async def token_create(self, code: str | None = None) -> ObjectToken:
|
||||||
"""Create a token."""
|
"""Create a token."""
|
||||||
if self._connection is None:
|
if self._connection is None:
|
||||||
raise RuntimeError("Not connected to the database")
|
raise RuntimeError("Not connected to the database")
|
||||||
code = get_hash(str(time.time()).encode() + os.urandom(64))
|
if code is None:
|
||||||
|
code = get_hash(str(time.time()).encode() + os.urandom(64))
|
||||||
|
else:
|
||||||
|
if len(code) < 2 or len(code) > 16:
|
||||||
|
raise RuntimeError("Token must be 2-16 symbols long")
|
||||||
|
if not all(ord(c) < 128 and (c.islower() or c.isdigit()) for c in code):
|
||||||
|
raise RuntimeError("Only digits and ASCII lowercase letters are allowed")
|
||||||
create_time = time.time()
|
create_time = time.time()
|
||||||
statement = """
|
statement = """
|
||||||
INSERT INTO tokens (code, name, created_at, last_access_at)
|
INSERT INTO tokens (code, name, created_at, last_access_at)
|
||||||
@@ -329,6 +335,17 @@ class Database:
|
|||||||
except:
|
except:
|
||||||
self._logger.error(traceback.format_exc())
|
self._logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
async def token_set_last_access(self, code: str, timestamp: float) -> None:
|
||||||
|
"""Set last_access_at for the token."""
|
||||||
|
if self._connection is None:
|
||||||
|
raise RuntimeError("Not connected to the database")
|
||||||
|
try:
|
||||||
|
statement = "UPDATE tokens SET last_access_at=? WHERE code=?"
|
||||||
|
await self._connection.execute(statement, (timestamp, code))
|
||||||
|
await self._connection.commit()
|
||||||
|
except:
|
||||||
|
self._logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
async def ban_create(self, ip: str, expires_at: float, reason: str) -> None:
|
async def ban_create(self, ip: str, expires_at: float, reason: str) -> None:
|
||||||
"""Save information about banned IP address. Replaces existing IPs."""
|
"""Save information about banned IP address. Replaces existing IPs."""
|
||||||
|
|||||||
2
main.py
2
main.py
@@ -55,7 +55,7 @@ async def main() -> None:
|
|||||||
if not await db.connect():
|
if not await db.connect():
|
||||||
util.log_error("Can't connect to the database!")
|
util.log_error("Can't connect to the database!")
|
||||||
return
|
return
|
||||||
bot.start()
|
await bot.start()
|
||||||
await web_server.start()
|
await web_server.start()
|
||||||
|
|
||||||
# wait for Ctrl+C
|
# wait for Ctrl+C
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ idna==3.19
|
|||||||
Jinja2==3.1.6
|
Jinja2==3.1.6
|
||||||
jsonschema==4.26.0
|
jsonschema==4.26.0
|
||||||
jsonschema-specifications==2025.9.1
|
jsonschema-specifications==2025.9.1
|
||||||
mab @ git+https://git.tyukalov.su/nikita/mab@c3046307c7bc4e65113aab62b07bad2a148b49e5
|
mab @ git+https://git.tyukalov.su/nikita/mab@v0.5.0
|
||||||
markdown-it-py==4.2.0
|
markdown-it-py==4.2.0
|
||||||
MarkupSafe==3.0.3
|
MarkupSafe==3.0.3
|
||||||
matrix-nio==0.26.0
|
matrix-nio==0.26.0
|
||||||
@@ -42,6 +42,7 @@ mdurl==0.1.2
|
|||||||
multidict==6.7.1
|
multidict==6.7.1
|
||||||
packaging==26.3
|
packaging==26.3
|
||||||
peewee==3.19.0
|
peewee==3.19.0
|
||||||
|
pillow==12.3.0
|
||||||
propcache==0.5.2
|
propcache==0.5.2
|
||||||
pycryptodome==3.23.0
|
pycryptodome==3.23.0
|
||||||
pydantic==2.13.5
|
pydantic==2.13.5
|
||||||
@@ -51,6 +52,7 @@ pydantic_core==2.46.5
|
|||||||
Pygments==2.21.0
|
Pygments==2.21.0
|
||||||
pyproject_hooks==1.2.0
|
pyproject_hooks==1.2.0
|
||||||
python-dotenv==1.2.3
|
python-dotenv==1.2.3
|
||||||
|
python-magic==0.4.27
|
||||||
python-multipart==0.0.32
|
python-multipart==0.0.32
|
||||||
python-socks==3.0.0
|
python-socks==3.0.0
|
||||||
PyYAML==6.0.3
|
PyYAML==6.0.3
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
content={"detail": "No such token"}
|
content={"detail": "No such token"}
|
||||||
)
|
)
|
||||||
|
await database.token_set_last_access(token, time.time())
|
||||||
request.state.token = token_data
|
request.state.token = token_data
|
||||||
return await call_next(request)
|
return await call_next(request)
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ async def _get_notify(request: Request, params: ModelGetNotify = Depends()):
|
|||||||
text += html.escape(params.text)
|
text += html.escape(params.text)
|
||||||
# try to send the notification
|
# try to send the notification
|
||||||
try:
|
try:
|
||||||
notification_id = await matrix.send_text_to_room(
|
notification_id = await matrix.send_text(
|
||||||
room.matrix_id,
|
room.matrix_id,
|
||||||
text,
|
text,
|
||||||
is_html=True
|
is_html=True
|
||||||
|
|||||||
Reference in New Issue
Block a user