Единый CHAT_ID и доступ только участникам чата
This commit is contained in:
parent
a96793e755
commit
9f33831971
|
|
@ -18,11 +18,10 @@ DB_POOL_MAX=10
|
||||||
DB_STATEMENT_TIMEOUT_MS=15000
|
DB_STATEMENT_TIMEOUT_MS=15000
|
||||||
|
|
||||||
# Чаты
|
# Чаты
|
||||||
# Основной чат клана (prod) и тестовый чат
|
# Основной чат клана (доступ в бота — только его участникам)
|
||||||
PROD_CHAT_ID=
|
CHAT_ID=
|
||||||
TEST_CHAT_ID=
|
# Чат для служебных уведомлений/логов (опционально)
|
||||||
# Личный чат для алертов об ошибках
|
ALERT_CHAT_ID=0
|
||||||
ALERT_CHAT_ID=
|
|
||||||
|
|
||||||
# Telegram ID администраторов (через запятую)
|
# Telegram ID администраторов (через запятую)
|
||||||
ADMIN_IDS=
|
ADMIN_IDS=
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ cd DispelEvilBot
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp env/test.env.example env/test.env
|
cp env/test.env.example env/test.env
|
||||||
# заполнить: BOT_TOKEN, DB_PASSWORD, тестовые чаты (TEST_CHAT_ID/PROD_CHAT_ID), ADMIN_IDS
|
# заполнить: BOT_TOKEN, DB_PASSWORD, CHAT_ID (основной чат), ALERT_CHAT_ID (опц.), ADMIN_IDS (опц.)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. Логин в registry (если пакет приватный)
|
## 3. Логин в registry (если пакет приватный)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ router = Router(name="member_events")
|
||||||
async def bot_added_handler(event: ChatMemberUpdated) -> None:
|
async def bot_added_handler(event: ChatMemberUpdated) -> None:
|
||||||
"""Бот добавлен в чат."""
|
"""Бот добавлен в чат."""
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
if event.chat.id not in {settings.prod_chat_id, settings.test_chat_id}:
|
if event.chat.id not in {settings.chat_id, settings.alert_chat_id}:
|
||||||
await event.bot.send_message(chat_id=event.chat.id, text=system.MSG_BOT_FOREIGN_CHAT)
|
await event.bot.send_message(chat_id=event.chat.id, text=system.MSG_BOT_FOREIGN_CHAT)
|
||||||
await event.bot.leave_chat(event.chat.id)
|
await event.bot.leave_chat(event.chat.id)
|
||||||
else:
|
else:
|
||||||
|
|
@ -35,6 +35,8 @@ async def welcome_handler(event: ChatMemberUpdated) -> None:
|
||||||
user = event.new_chat_member.user
|
user = event.new_chat_member.user
|
||||||
if user.is_bot:
|
if user.is_bot:
|
||||||
return
|
return
|
||||||
|
if event.chat.id != get_settings().chat_id:
|
||||||
|
return
|
||||||
|
|
||||||
await users_service.register_from_event(event)
|
await users_service.register_from_event(event)
|
||||||
|
|
||||||
|
|
@ -61,21 +63,16 @@ async def goodbye_handler(event: ChatMemberUpdated) -> None:
|
||||||
user = event.from_user
|
user = event.from_user
|
||||||
if user is None or user.is_bot:
|
if user is None or user.is_bot:
|
||||||
return
|
return
|
||||||
if event.chat.id == settings.test_chat_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
await users_service.delete_user(user.id)
|
await users_service.delete_user(user.id)
|
||||||
logger.info("Удалён пользователь %s после выхода из чата", user.id)
|
logger.info("Удалён пользователь %s после выхода из чата", user.id)
|
||||||
|
|
||||||
|
if not settings.alert_chat_id:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
await event.bot.send_message(
|
await event.bot.send_message(
|
||||||
chat_id=settings.test_chat_id,
|
chat_id=settings.alert_chat_id,
|
||||||
text=f"[goodbye_handler]\nУдалил {user.username or user.id}!",
|
text=f"[goodbye] {user.username or user.id} покинул чат {event.chat.title}",
|
||||||
)
|
)
|
||||||
if settings.alert_chat_id:
|
|
||||||
await event.bot.send_message(
|
|
||||||
chat_id=settings.alert_chat_id,
|
|
||||||
text=f"Пользователь {user.username or user.id} покинул чат {event.chat.title}",
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Не удалось отправить уведомление о выходе пользователя")
|
logger.exception("Не удалось отправить уведомление о выходе пользователя")
|
||||||
|
|
|
||||||
|
|
@ -39,15 +39,9 @@ async def _is_member(bot: Bot, user_id: int) -> bool:
|
||||||
|
|
||||||
|
|
||||||
async def check_user(message: Message) -> bool:
|
async def check_user(message: Message) -> bool:
|
||||||
"""Пропустить пользователя: проверить членство и обновить его запись.
|
"""Пропустить пользователя: доступ только участникам основного чата (или админам)."""
|
||||||
|
|
||||||
В тестовом чате доступ разрешён всегда.
|
|
||||||
"""
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
if message.chat.id == settings.test_chat_id:
|
|
||||||
return True
|
|
||||||
|
|
||||||
user = message.from_user
|
user = message.from_user
|
||||||
if user is None:
|
if user is None:
|
||||||
return False
|
return False
|
||||||
|
|
@ -63,18 +57,21 @@ async def check_user(message: Message) -> bool:
|
||||||
|
|
||||||
|
|
||||||
async def _notify_denied(message: Message) -> None:
|
async def _notify_denied(message: Message) -> None:
|
||||||
|
"""Сообщить в чат логов (если задан) о попытке доступа извне."""
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
if not settings.alert_chat_id:
|
||||||
|
return
|
||||||
|
|
||||||
username = message.from_user.username if message.from_user else None
|
username = message.from_user.username if message.from_user else None
|
||||||
try:
|
try:
|
||||||
await message.bot.send_message(
|
await message.bot.send_message(
|
||||||
chat_id=settings.test_chat_id,
|
chat_id=settings.alert_chat_id,
|
||||||
text=f"[check_user]\nСтучится в чат {username}!",
|
text=f"[check_user]\nСтучится в чат {username}!",
|
||||||
)
|
)
|
||||||
if settings.alert_chat_id:
|
await message.bot.forward_message(
|
||||||
await message.bot.forward_message(
|
chat_id=settings.alert_chat_id,
|
||||||
chat_id=settings.alert_chat_id,
|
from_chat_id=message.chat.id,
|
||||||
from_chat_id=message.chat.id,
|
message_id=message.message_id,
|
||||||
message_id=message.message_id,
|
)
|
||||||
)
|
|
||||||
except Exception: # уведомление не должно ломать обработку
|
except Exception: # уведомление не должно ломать обработку
|
||||||
logger.exception("Не удалось отправить уведомление о запрете доступа")
|
logger.exception("Не удалось отправить уведомление о запрете доступа")
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,9 @@ async def _send_broadcast(bot: Bot, template: str, subscribers: list[dict]) -> N
|
||||||
|
|
||||||
async def _tick(bot: Bot, now: datetime) -> None:
|
async def _tick(bot: Bot, now: datetime) -> None:
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
if now.minute == 0:
|
if now.minute == 0 and settings.alert_chat_id:
|
||||||
await bot.send_message(chat_id=settings.test_chat_id, text="Я ЖЫФ!")
|
# Пинг «жив» в чат логов (если задан).
|
||||||
|
await bot.send_message(chat_id=settings.alert_chat_id, text="Я ЖЫФ!")
|
||||||
|
|
||||||
for schedule in SCHEDULES:
|
for schedule in SCHEDULES:
|
||||||
if schedule.matches(now):
|
if schedule.matches(now):
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,12 @@ class Settings(BaseSettings):
|
||||||
db_pool_max: int = 10
|
db_pool_max: int = 10
|
||||||
db_statement_timeout_ms: int = 15000
|
db_statement_timeout_ms: int = 15000
|
||||||
|
|
||||||
prod_chat_id: int = 0
|
#: Основной чат клана (единственный; контуры разделяются отдельными .env).
|
||||||
test_chat_id: int = 0
|
chat_id: int = 0
|
||||||
|
#: Доп. чат для служебных уведомлений/логов (опционально).
|
||||||
alert_chat_id: int = 0
|
alert_chat_id: int = 0
|
||||||
|
|
||||||
|
#: Telegram-ID, которым доступ разрешён в обход проверки членства.
|
||||||
admin_ids: list[int] = Field(default_factory=list)
|
admin_ids: list[int] = Field(default_factory=list)
|
||||||
|
|
||||||
timezone: str = "Europe/Moscow"
|
timezone: str = "Europe/Moscow"
|
||||||
|
|
@ -48,6 +50,14 @@ class Settings(BaseSettings):
|
||||||
#: Файл-heartbeat для Docker healthcheck (обновляется ботом периодически).
|
#: Файл-heartbeat для Docker healthcheck (обновляется ботом периодически).
|
||||||
heartbeat_path: str = "/tmp/dispelevilbot.heartbeat"
|
heartbeat_path: str = "/tmp/dispelevilbot.heartbeat"
|
||||||
|
|
||||||
|
@field_validator("chat_id", "alert_chat_id", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _empty_int_to_zero(cls, value: object) -> object:
|
||||||
|
"""Пустое значение в .env трактуем как 0 (а не ошибку валидации)."""
|
||||||
|
if value is None or value == "":
|
||||||
|
return 0
|
||||||
|
return value
|
||||||
|
|
||||||
@field_validator("admin_ids", mode="before")
|
@field_validator("admin_ids", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parse_admin_ids(cls, value: object) -> object:
|
def _parse_admin_ids(cls, value: object) -> object:
|
||||||
|
|
@ -65,11 +75,6 @@ class Settings(BaseSettings):
|
||||||
def is_prod(self) -> bool:
|
def is_prod(self) -> bool:
|
||||||
return self.app_env.strip().lower() in {"prod", "production"}
|
return self.app_env.strip().lower() in {"prod", "production"}
|
||||||
|
|
||||||
@property
|
|
||||||
def chat_id(self) -> int:
|
|
||||||
"""Основной чат клана для текущего окружения."""
|
|
||||||
return self.prod_chat_id if self.is_prod else self.test_chat_id
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tzinfo(self) -> ZoneInfo:
|
def tzinfo(self) -> ZoneInfo:
|
||||||
return ZoneInfo(self.timezone)
|
return ZoneInfo(self.timezone)
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,10 @@ DB_POOL_MIN=1
|
||||||
DB_POOL_MAX=10
|
DB_POOL_MAX=10
|
||||||
DB_STATEMENT_TIMEOUT_MS=15000
|
DB_STATEMENT_TIMEOUT_MS=15000
|
||||||
|
|
||||||
PROD_CHAT_ID=
|
# Основной чат этого контура (доступ в бота — только его участникам)
|
||||||
TEST_CHAT_ID=
|
CHAT_ID=
|
||||||
ALERT_CHAT_ID=
|
# Чат для служебных уведомлений/логов (опционально; пусто или 0 — выключено)
|
||||||
|
ALERT_CHAT_ID=0
|
||||||
|
# Telegram-ID админов (доступ в обход проверки членства), через запятую; можно пусто
|
||||||
ADMIN_IDS=
|
ADMIN_IDS=
|
||||||
TIMEZONE=Europe/Moscow
|
TIMEZONE=Europe/Moscow
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ DB_POOL_MIN=1
|
||||||
DB_POOL_MAX=10
|
DB_POOL_MAX=10
|
||||||
DB_STATEMENT_TIMEOUT_MS=15000
|
DB_STATEMENT_TIMEOUT_MS=15000
|
||||||
|
|
||||||
PROD_CHAT_ID=
|
# Основной чат этого контура (доступ в бота — только его участникам)
|
||||||
TEST_CHAT_ID=
|
CHAT_ID=
|
||||||
ALERT_CHAT_ID=
|
# Чат для служебных уведомлений/логов (опционально; пусто или 0 — выключено)
|
||||||
|
ALERT_CHAT_ID=0
|
||||||
|
# Telegram-ID админов (доступ в обход проверки членства), через запятую; можно пусто
|
||||||
ADMIN_IDS=
|
ADMIN_IDS=
|
||||||
TIMEZONE=Europe/Moscow
|
TIMEZONE=Europe/Moscow
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue