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