Skip to content

Commit 9dc52de

Browse files
committed
fix: improve multi-group robustness to prevent bot crashes
- Remove RuntimeError in dm.py when all unrestriction attempts fail, replace with error log and friendly user message - Add try/except around get_user_status() in dm.py membership scan so one inaccessible group doesn't abort the entire DM handler - Add per-group try/except in verify_user() so a failure in one group doesn't prevent verification in other groups - Make get_group_config_for_update() resilient to uninitialized registry by catching RuntimeError and returning None - Fix ruff lint: remove unused imports and variables
1 parent e7098e4 commit 9dc52de

8 files changed

Lines changed: 54 additions & 42 deletions

File tree

src/bot/group_config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,11 @@ def get_group_config_for_update(update: Update) -> GroupConfig | None:
201201
"""
202202
if not update.effective_chat:
203203
return None
204-
return get_group_registry().get(update.effective_chat.id)
204+
try:
205+
return get_group_registry().get(update.effective_chat.id)
206+
except RuntimeError:
207+
logger.error("Group registry not initialized; skipping update")
208+
return None
205209

206210

207211
# Module-level singleton

src/bot/handlers/anti_spam.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ async def handle_new_user_spam(
215215
return
216216

217217
group_config = get_group_config_for_update(update)
218-
chat = update.effective_chat
219218
user = update.message.from_user
220219

221220
# Only process messages from monitored groups

src/bot/handlers/check.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from telegram.error import TimedOut
1414
from telegram.ext import ContextTypes
1515

16-
from bot.config import get_settings
1716
from bot.constants import (
1817
ADMIN_CHECK_ACTION_COMPLETE,
1918
ADMIN_CHECK_ACTION_INCOMPLETE,
@@ -248,7 +247,6 @@ async def handle_warn_callback(
248247
missing_items.append("username")
249248
missing_text = MISSING_ITEMS_SEPARATOR.join(missing_items) if missing_items else "profil"
250249

251-
settings = get_settings()
252250
registry = get_group_registry()
253251

254252
try:

src/bot/handlers/dm.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,14 @@ async def handle_dm(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
7171
member_groups = []
7272
for gc in registry.all_groups():
7373
logger.info(f"Checking user status in group_id={gc.group_id} for user_id={user.id}")
74-
user_status = await get_user_status(context.bot, gc.group_id, user.id)
74+
try:
75+
user_status = await get_user_status(context.bot, gc.group_id, user.id)
76+
except Exception:
77+
logger.warning(
78+
f"Failed to check user status in group {gc.group_id} for user {user.id}",
79+
exc_info=True,
80+
)
81+
continue
7582
if user_status is not None and user_status not in (ChatMemberStatus.LEFT, ChatMemberStatus.BANNED):
7683
member_groups.append((gc, user_status))
7784

@@ -174,6 +181,7 @@ async def handle_dm(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
174181
await update.message.reply_text(DM_ALREADY_UNRESTRICTED_MESSAGE)
175182
else:
176183
# All unrestriction attempts failed
177-
raise RuntimeError(
178-
f"Failed to unrestrict user {user.id} in any group"
184+
logger.error(f"Failed to unrestrict user {user.id} in any group")
185+
await update.message.reply_text(
186+
"❌ Gagal membuka pembatasan. Silakan hubungi admin grup."
179187
)

src/bot/handlers/verify.py

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,35 +51,42 @@ async def verify_user(
5151
# Unrestrict user and delete warnings in all monitored groups
5252
total_deleted = 0
5353
for group_config in registry.all_groups():
54-
# Unrestrict user if they are restricted
5554
try:
56-
await unrestrict_user(bot, group_config.group_id, target_user_id)
57-
logger.info(f"Unrestricted user {target_user_id} in group {group_config.group_id} during verification")
58-
except BadRequest as e:
59-
# User might not be restricted or not in group - that's okay
60-
logger.info(f"Could not unrestrict user {target_user_id} in group {group_config.group_id}: {e}")
61-
62-
# Delete all warning records for this user in this group
63-
deleted_count = db.delete_user_warnings(target_user_id, group_config.group_id)
64-
total_deleted += deleted_count
65-
66-
# Send notification to warning topic if user had previous warnings
67-
if deleted_count > 0:
68-
# Get user info for proper mention
69-
user_info = await bot.get_chat(target_user_id)
70-
user_mention = get_user_mention(user_info)
71-
72-
# Send clearance message to warning topic
73-
clearance_message = VERIFICATION_CLEARANCE_MESSAGE.format(
74-
user_mention=user_mention
55+
# Unrestrict user if they are restricted
56+
try:
57+
await unrestrict_user(bot, group_config.group_id, target_user_id)
58+
logger.info(f"Unrestricted user {target_user_id} in group {group_config.group_id} during verification")
59+
except BadRequest as e:
60+
# User might not be restricted or not in group - that's okay
61+
logger.info(f"Could not unrestrict user {target_user_id} in group {group_config.group_id}: {e}")
62+
63+
# Delete all warning records for this user in this group
64+
deleted_count = db.delete_user_warnings(target_user_id, group_config.group_id)
65+
total_deleted += deleted_count
66+
67+
# Send notification to warning topic if user had previous warnings
68+
if deleted_count > 0:
69+
# Get user info for proper mention
70+
user_info = await bot.get_chat(target_user_id)
71+
user_mention = get_user_mention(user_info)
72+
73+
# Send clearance message to warning topic
74+
clearance_message = VERIFICATION_CLEARANCE_MESSAGE.format(
75+
user_mention=user_mention
76+
)
77+
await bot.send_message(
78+
chat_id=group_config.group_id,
79+
message_thread_id=group_config.warning_topic_id,
80+
text=clearance_message,
81+
parse_mode="Markdown"
82+
)
83+
logger.info(f"Sent clearance notification to warning topic for user {target_user_id} in group {group_config.group_id}")
84+
except Exception:
85+
logger.warning(
86+
f"Verification failed for group {group_config.group_id} for user {target_user_id}",
87+
exc_info=True,
7588
)
76-
await bot.send_message(
77-
chat_id=group_config.group_id,
78-
message_thread_id=group_config.warning_topic_id,
79-
text=clearance_message,
80-
parse_mode="Markdown"
81-
)
82-
logger.info(f"Sent clearance notification to warning topic for user {target_user_id} in group {group_config.group_id}")
89+
continue
8390

8491
if total_deleted > 0:
8592
logger.info(f"Deleted {total_deleted} total warning record(s) for user {target_user_id}")

tests/test_check.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,6 @@ async def test_warn_callback_success(
416416
mock_context.bot.get_chat.return_value = mock_chat
417417

418418
with (
419-
patch("bot.handlers.check.get_settings", return_value=mock_settings),
420419
patch(
421420
"bot.handlers.check.get_group_registry",
422421
return_value=mock_registry,
@@ -459,7 +458,6 @@ async def test_warn_callback_success_missing_photo_only(
459458
mock_context.bot.get_chat.return_value = mock_chat
460459

461460
with (
462-
patch("bot.handlers.check.get_settings", return_value=mock_settings),
463461
patch(
464462
"bot.handlers.check.get_group_registry",
465463
return_value=mock_registry,
@@ -534,7 +532,6 @@ async def test_warn_callback_send_message_error(
534532
mock_context.bot.send_message.side_effect = Exception("Failed to send")
535533

536534
with (
537-
patch("bot.handlers.check.get_settings", return_value=mock_settings),
538535
patch(
539536
"bot.handlers.check.get_group_registry",
540537
return_value=mock_registry,
@@ -566,7 +563,6 @@ async def test_warn_callback_timeout(
566563
mock_context.bot.send_message.side_effect = TimedOut()
567564

568565
with (
569-
patch("bot.handlers.check.get_settings", return_value=mock_settings),
570566
patch(
571567
"bot.handlers.check.get_group_registry",
572568
return_value=mock_registry,
@@ -597,7 +593,6 @@ async def test_warn_callback_get_chat_timeout(
597593
mock_context.bot.get_chat.side_effect = TimedOut()
598594

599595
with (
600-
patch("bot.handlers.check.get_settings", return_value=mock_settings),
601596
patch(
602597
"bot.handlers.check.get_group_registry",
603598
return_value=mock_registry,

tests/test_dm_handler.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,5 +460,7 @@ async def test_unrestrict_user_exception_logged_and_raised(
460460
side_effect=Exception("test error"),
461461
),
462462
):
463-
with pytest.raises(RuntimeError, match="Failed to unrestrict user 12345 in any group"):
464-
await handle_dm(mock_update, mock_context)
463+
await handle_dm(mock_update, mock_context)
464+
mock_update.message.reply_text.assert_called_with(
465+
"❌ Gagal membuka pembatasan. Silakan hubungi admin grup."
466+
)

tests/test_group_config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import json
44
import tempfile
55
from datetime import timedelta
6-
from pathlib import Path
76
from unittest.mock import MagicMock, patch
87

98
import pytest

0 commit comments

Comments
 (0)