Browse Source

Дополнить документацию.

master
Дмитрий 3 years ago
parent
commit
a37910b8d7
  1. 47
      main.py
  2. 132
      services.py

47
main.py

@ -17,8 +17,19 @@ dp = Dispatcher(bot)
@dp.message_handler(commands=["start", "help"])
@services.auth
async def send_welcome(message: types.Message):
"""Справка по командам."""
async def send_welcome(message: types.Message) -> None:
r"""Справка по командам.
Команды бота: `/start`, `/help'.
Возвращает справку по командам. Текст берет из файла
`static/help_msg.txt`. Доступна только пользователям из белого
списка.
Parameters
----------
message: types.Message
Сообщение пользователя. Библиотека aiogram.
"""
await message.reply(
services.get_static("static/help_msg.txt"),
reply=False,
@ -122,7 +133,6 @@ async def add_me(message: types.Message) -> None:
Когда бот получает токен, он проверяет его на валидность (есть ли
такой вообще, не истек ли) и высылает разовую ссылку на вступление
в группу.
"""
user_token = services.get_text_from_command(message)
@ -154,9 +164,11 @@ async def add_me(message: types.Message) -> None:
async def stop_and_panic(message: types.Message) -> None:
"""Panic-button для бота.
Команда `/stop`
Доступна белому списку.
При нажатии:
- кидает юзера в чс чата и удаляет его,
- удаляет из белого списка.
- кидает юзера в чс чата и удаляет его из чата
- удаляет из белого списка.
"""
u_id = message["from"]["id"]
services.delete_user_from_allowed_list(u_id)
@ -168,7 +180,12 @@ async def stop_and_panic(message: types.Message) -> None:
@dp.message_handler(commands=["wlc"])
@services.only_admins
async def wlc(message: types.Message) -> None:
"""Тестирование приветственного сообщения."""
"""Тестирование приветственного сообщения.
Команда `/wlc`
Доступна только админам.
Команда для тестирования привественного сообщения.
"""
await message.reply(
services.get_static("static/welcome_msg.txt"),
reply=False,
@ -180,7 +197,12 @@ async def wlc(message: types.Message) -> None:
@dp.message_handler(commands=["info", "version"])
@services.only_admins
async def get_info(message: types.Message) -> None:
"""Вовзращает версию бота."""
"""Выводит информацию о боте и окружении.
Команды: `/info`, `/version`
Доступна только админам.
Бот присылает свою версию, время жизни постов и вопросов.
"""
msg = f"Версия бота: *{settings.VERSION}*\n"
msg += f"Время жизни постов: *{settings.DELAY_TIME_POST} минут*\n"
msg += f"Время жизни вопросов: *{settings.DELAY_TIME_Q} минут*\n"
@ -201,9 +223,16 @@ async def test_admins(message: types.Message):
)
# https://stackoverflow.com/questions/67637631/create-a-background-process-using-aiogram
async def messages_cleanup() -> None:
"""Зачистка пропущенных сообщений. При запуске бота проверяет, есть ли не удаленные сообщения требующие удаления, удаляет их."""
"""Удаление "забытых" сообщений.
При запуске вызывает функцию удаления старых сообщений. Постоянно
висит в фоне и вызывает функцию удаления каждые 600 мс.
See Also
--------
https://stackoverflow.com/questions/67637631/create-a-background-process-using-aiogram
"""
while True:
await services.delete_old_messages(bot)
await asyncio.sleep(600)

132
services.py

@ -16,17 +16,18 @@ from aiogram import Bot, types
from functools import wraps
# не очень красивый способ создание общего объекта ORM для всех
# функций модуля
engine = create_engine("sqlite:///" + str(settings.DB_PATH))
Session = sessionmaker(bind=engine)
session = Session()
def auth(func):
"""Аутентификация.
"""Декоратор для аутентификации.
Проверяем, что у юзера есть полномочия на выполнение действия - он
должен быть в белом списке.
"""
async def wrapper(message):
@ -47,7 +48,10 @@ def auth(func):
def only_admins(func):
"""Действия только для админов."""
"""Декоратора аутентификации для администраторов.
Данные действия с ботом могут выполнять только администраторы.
"""
async def wrapper(message):
uid = message["from"]["id"]
@ -61,7 +65,21 @@ def only_admins(func):
async def _clean_up(message_id: int, delay_min: int, bot: Bot) -> None:
"""Удаление сообщенния message_id через delay_min минут."""
"""Поставить таймер на удаление сообщения.
Внутренняя функция для удаления сообщения. Пишет в базу id
сообщения и время, когда его нужно удалить. Ждет `delay_min` и
пробует удалить сообщение.
Parameters
----------
message_id : int
Идентификатор сообщения, присвоенный ему Телеграмом.
delay_min : int
Количество минут, через которое это сообщение нужно удалить.
bot : Bot
Объект бота, через который сообщение будет отправленно в канал.
"""
exp = datetime.now() + timedelta(minutes=delay_min)
msg: Messages_to_delete = Messages_to_delete(
message_id=message_id, deletion_date=exp
@ -80,10 +98,16 @@ async def _clean_up(message_id: int, delay_min: int, bot: Bot) -> None:
def get_new_token() -> str:
"""Возвращает токен для приглашения пользователя.
"""Получить токен.
Длина токена 16 символов, срок действия 5 минут. Токен записывается в базу.
Возвращает токен для приглашения нового пользователя.
Returns
-------
token : str
Токен для доступа к группе. Длина - 16 символов. Длина токена
16 символов, срок действия 5 минут. Токен и его срок годности
записываются в базу.
"""
token = token_urlsafe(16)
exp = datetime.now() + timedelta(minutes=5)
@ -95,7 +119,22 @@ def get_new_token() -> str:
def check_token(token: str) -> bool:
"""Проверяет, находится ли токен в базе, возвращает True, если токен есть и не истек. затем удаляет токен из базы."""
"""Проверить валидность токена.
Токен считается валидным если он соотвествует сразу двум условиям:
он есть в базе, срок его действия не истек. После проверки токен
будет удален из базы.
Parameters
----------
token : str
Токен. Длина не проверяется.
Returns
-------
result : bool
Результат проверки токена.
"""
finded_token = session.query(Token).filter(Token.token == token).one_or_none()
if finded_token and not finded_token.expired():
@ -107,7 +146,16 @@ def check_token(token: str) -> bool:
def add_new_user_to_allowed_list(user_id: int) -> None:
"""Добавляет пользователя в белый список. Если пользователь в нем уже есть, то игнорирует ошибку и ведет себя так, словно его добавили впервые."""
"""Добавить пользователя в белый список.
Если пользователь в нем уже есть, то игнорирует ошибку и ведет
себя так, словно его добавили впервые.
Parameters
----------
user_id : int
Идентификатор пользователя.
"""
new_user: Allowed_user = Allowed_user(user_id=user_id, date_add=datetime.now())
try:
@ -119,7 +167,13 @@ def add_new_user_to_allowed_list(user_id: int) -> None:
def delete_user_from_allowed_list(user_id: str) -> None:
"""Удаляет пользователя из белого списка."""
"""Удалить пользователя из белого списка.
Parameters
----------
user_id : int
Идентификатор пользователя.
"""
user: Allowed_user = (
session.query(Allowed_user)
.filter(Allowed_user.user_id == user_id)
@ -131,14 +185,33 @@ def delete_user_from_allowed_list(user_id: str) -> None:
def get_static(name: str) -> str:
"""Возвращает содержимое из файла переданного в name."""
"""Считать содержимое файла.
Возвращает содержимое файла. Поддерживает Markdown.
Parameters
----------
name : str
Имя файла вместе с папкой. Рекомендуется размещать файлы с
текстом в папке `/static`.
Returns
-------
msg : str
Содержимое файла, переданное в `name`.
"""
with open(name, "r") as f:
msg = f.read()
return msg
async def delete_old_messages(bot: Bot) -> None:
"""Удаляет сообещния, если их delete_date меньше чем время сейчас."""
"""Удалить старые сообщения.
Удаляет сообщения, если их delete_date меньше чем время сейчас.
Если сообщения в чате не найдено - выводит сообщение в лог и
удалет сообщение из базы.
"""
msg_list: Messages_to_delete = (
session.query(Messages_to_delete).order_by(Messages_to_delete.id).all()
)
@ -160,7 +233,25 @@ async def delete_old_messages(bot: Bot) -> None:
def get_text_from_command(message: types.Message) -> str:
"""Вернет текст из команды для бота (но не саму команду). Парсит message["entities"]."""
"""Получить текст сообщения, отправленного боту.
Вернет текст из команды для бота (но не саму команду).
Parameters
----------
message : types.Message
Сообщения, из которого нужно извлечь текст.
Returns
-------
text : str
Текст (тело) сообщения без команды.
Notes
-----
Парсит message["entities"], поэтому в качестве аргумента не нужна
команда, а только объект сообщения.
"""
command_length = int(message["entities"][0]["length"])
return message.text[command_length:].strip()
@ -168,7 +259,22 @@ def get_text_from_command(message: types.Message) -> str:
async def send_message_to_chat(
msg_date: datetime, delete_after: int, text: str, bot: Bot
) -> None:
"""Отправляет сообщение в чат, ставит таймер на удаление через delete_after минут."""
"""Отправить сообщение в чат.
Отправляет сообщение в чат, ставит таймер на удаление.
Parameters
----------
msg_date : datetime
Время отправки сообщения. На его основе высчитывается время
удаления.
delete_after : int
Количество минут, после которого нужно удалить сообщение.
text : str
Сообщение для отправки в канал.
bot : Bot
Объект бота, через которого будет отправлено сообщение.
"""
deletion_time = msg_date + timedelta(minutes=delete_after)
text += f"\n\nБудет удалено в *{deletion_time}*"

Loading…
Cancel
Save