1313)
1414
1515from .. import strings
16+ from ..db .base import AsyncRepository
1617from ..services .captcha import CaptchaService
1718from ..services .moderation import ModerationService
1819
1920logger = logging .getLogger (__name__ )
2021
22+ DEFAULT_WELCOME_DELAY_MINUTES = 5
23+
24+
25+ async def _track_user (repo : AsyncRepository , user : object ) -> None :
26+ """Track a Telegram user in the known_users table."""
27+ await repo .upsert_known_user (
28+ user_id = user .id , # type: ignore[attr-defined]
29+ username = getattr (user , "username" , None ),
30+ first_name = getattr (user , "first_name" , None ),
31+ last_name = getattr (user , "last_name" , None ),
32+ )
33+
2134
2235def create_welcome_handlers (captcha_service : CaptchaService ) -> list :
2336 """Create welcome and captcha handlers."""
@@ -34,13 +47,29 @@ def create_welcome_handlers(captcha_service: CaptchaService) -> list:
3447 ]
3548
3649
50+ async def _delete_welcome_message (context : ContextTypes .DEFAULT_TYPE ) -> None :
51+ """Job callback: delete a welcome message after the configured delay."""
52+ job = context .job
53+ if job is None or job .data is None :
54+ return
55+ data : tuple [int , int ] = job .data # type: ignore[assignment]
56+ chat_id , message_id = data
57+ try :
58+ await context .bot .delete_message (chat_id = chat_id , message_id = message_id )
59+ except Exception as e :
60+ logger .debug (
61+ "Could not delete welcome message %s in chat %s: %s" , message_id , chat_id , e
62+ )
63+
64+
3765async def _handle_new_member (
3866 update : Update ,
3967 context : ContextTypes .DEFAULT_TYPE ,
4068) -> None :
4169 """Handle new chat members: restrict and send welcome with captcha instructions."""
4270 captcha_service : CaptchaService = context .bot_data ["captcha_service" ]
4371 moderation_service : ModerationService = context .bot_data ["moderation_service" ]
72+ repository : AsyncRepository = context .bot_data ["repository" ]
4473 result = update .chat_member
4574 if result is None :
4675 return
@@ -64,6 +93,9 @@ async def _handle_new_member(
6493
6594 await moderation_service .register_chat (chat .id )
6695
96+ # Track the user
97+ await _track_user (repository , user )
98+
6799 if user .is_bot :
68100 return
69101
@@ -90,6 +122,10 @@ async def _handle_new_member(
90122
91123 await captcha_service .add_pending (user .id , chat .id )
92124
125+ # Skip welcome if user was already welcomed in this chat
126+ if await captcha_service .has_been_welcomed (user .id , chat .id ):
127+ return
128+
93129 bot_me = await context .bot .get_me ()
94130 bot_username = bot_me .username or "bot"
95131
@@ -99,17 +135,39 @@ async def _handle_new_member(
99135 else :
100136 template = captcha_service .get_default_welcome_template (bot_username )
101137
102- formatted = captcha_service .format_welcome_message (template , user , chat , bot_username )
138+ formatted = captcha_service .format_welcome_message (
139+ template , user , chat , bot_username
140+ )
103141 text , keyboard = captcha_service .parse_button_urls (formatted )
104142
105143 try :
106- await context .bot .send_message (
144+ sent = await context .bot .send_message (
107145 chat_id = chat .id ,
108146 text = text ,
109147 reply_markup = keyboard ,
110148 )
111149 except Exception as e :
112150 logger .warning ("Could not send welcome to chat %s: %s" , chat .id , e )
151+ return
152+
153+ # Mark user as welcomed in this chat
154+ await captcha_service .mark_welcomed (user .id , chat .id )
155+
156+ # Store welcome message -> user mapping for ban-by-reply
157+ welcome_map = context .bot_data .setdefault ("welcome_message_map" , {})
158+ welcome_map [(chat .id , sent .message_id )] = user .id
159+
160+ # Schedule auto-deletion of the welcome message
161+ delay_minutes = await captcha_service .get_welcome_delay (chat .id )
162+ if delay_minutes is None :
163+ delay_minutes = DEFAULT_WELCOME_DELAY_MINUTES
164+ if delay_minutes > 0 and context .job_queue is not None :
165+ context .job_queue .run_once (
166+ _delete_welcome_message ,
167+ when = delay_minutes * 60 ,
168+ data = (chat .id , sent .message_id ),
169+ name = f"del_welcome_{ chat .id } _{ sent .message_id } " ,
170+ )
113171
114172
115173async def _handle_start (
@@ -118,6 +176,7 @@ async def _handle_start(
118176) -> None :
119177 """Handle /start command, including deep link for verification."""
120178 captcha_service : CaptchaService = context .bot_data ["captcha_service" ]
179+ repository : AsyncRepository = context .bot_data ["repository" ]
121180 message = update .message
122181 if message is None :
123182 return
@@ -130,6 +189,9 @@ async def _handle_start(
130189 if user is None :
131190 return
132191
192+ # Track the user
193+ await _track_user (repository , user )
194+
133195 args = context .args
134196 if args and args [0 ] == "verify" :
135197 rules_url = captcha_service .get_rules_url ()
@@ -192,6 +254,7 @@ async def _handle_private_message(
192254) -> None :
193255 """Handle private messages: check for secret command and verify user globally."""
194256 captcha_service : CaptchaService = context .bot_data ["captcha_service" ]
257+ repository : AsyncRepository = context .bot_data ["repository" ]
195258 message = update .message
196259 if message is None or message .text is None :
197260 return
@@ -200,6 +263,9 @@ async def _handle_private_message(
200263 if user is None :
201264 return
202265
266+ # Track the user
267+ await _track_user (repository , user )
268+
203269 if not captcha_service .is_secret_command (message .text ):
204270 await message .reply_text (strings .VERIFY_UNKNOWN_COMMAND )
205271 return
0 commit comments