11import re
22from dataclasses import dataclass
3+ from html import escape as html_escape
4+ from typing import Any
35
46from telegram import Message , Update
57from telegram .ext import (
68 ContextTypes ,
79)
810
11+ from src import localizer
912from src .flows .parsers .tg import parse_telegram_source
1013from src .flows .parsers .vk import parse_vk_source
1114from src .storage .constants import MemeSourceStatus , MemeSourceType
3336_MEME_SOURCE_LINK_RE = re .compile (MEME_SOURCE_LINK_REGEXP )
3437
3538
39+ def _t (key : str , lang : str | None , ** kwargs : object ) -> str :
40+ text = localizer .t (key , lang )
41+ return text .format (** kwargs ) if kwargs else text
42+
43+
3644@dataclass (frozen = True )
3745class MemeSourceLink :
3846 url : str
@@ -78,13 +86,15 @@ def parse_meme_source_status_callback_data(data: str) -> tuple[int, str]:
7886
7987
8088async def handle_meme_source_link (update : Update , context : ContextTypes .DEFAULT_TYPE ) -> None :
81- if await get_moderator_user_info (update .effective_user .id ) is None :
82- await update .message .reply_text ("Only moderators can manage meme sources." )
89+ moderator = await get_moderator_user_info (update .effective_user .id )
90+ lang = _get_moderator_lang (update , moderator )
91+ if moderator is None :
92+ await update .message .reply_text (_t ("moderator.meme_source.only_moderators_manage" , lang ))
8393 return
8494
8595 link = parse_meme_source_link (update .message .text )
8696 if link is None :
87- await update .message .reply_text ("Unsupported meme source" )
97+ await update .message .reply_text (_t ( "moderator.meme_source.unsupported_source" , lang ) )
8898 return
8999
90100 meme_source = await get_or_create_meme_source (
@@ -94,17 +104,19 @@ async def handle_meme_source_link(update: Update, context: ContextTypes.DEFAULT_
94104 added_by = update .effective_user .id ,
95105 )
96106
97- await meme_source_admin_pipeline (meme_source , update )
107+ await meme_source_admin_pipeline (meme_source , update , lang )
98108
99109
100110async def handle_meme_source_language_selection (
101111 update : Update , context : ContextTypes .DEFAULT_TYPE
102112) -> None :
103113 user_id = update .effective_user .id
104- if await get_moderator_user_info (user_id ) is None :
114+ moderator = await get_moderator_user_info (user_id )
115+ lang = _get_moderator_lang (update , moderator )
116+ if moderator is None :
105117 await update .callback_query .answer (
106- "🤷♀️ Only moderators can change meme source language 🤷♂️"
107- ) # noqa: E501
118+ _t ( "moderator.meme_source.only_moderators_language" , lang )
119+ )
108120 return
109121
110122 args = update .callback_query .data .split (":" )
@@ -118,30 +130,34 @@ async def handle_meme_source_language_selection(
118130 trigger_parse = False ,
119131 )
120132 except MemeSourceNotFoundError :
121- await update .callback_query .answer ("Meme source not found" )
133+ await update .callback_query .answer (_t ( "moderator.meme_source.not_found" , lang ) )
122134 return
123135
124136 await log (
125137 f"ℹ️ MemeSource ${ meme_source_id } : set_lang={ lang_code } (by { user_id } )" ,
126138 context .bot ,
127139 )
128140
129- await update .callback_query .answer (f"Meme source lang is { lang_code } now" )
130- await meme_source_admin_pipeline (result ["source" ], update )
141+ await update .callback_query .answer (
142+ _t ("moderator.meme_source.language_updated" , lang , language = lang_code )
143+ )
144+ await meme_source_admin_pipeline (result ["source" ], update , lang )
131145
132146
133147async def handle_meme_source_change_status (
134148 update : Update , context : ContextTypes .DEFAULT_TYPE
135149) -> None :
136150 user_id = update .effective_user .id
137- if await get_moderator_user_info (user_id ) is None :
138- await update .callback_query .answer ("🤷♀️ Only moderators can change meme source status 🤷♂️" ) # noqa: E501
151+ moderator = await get_moderator_user_info (user_id )
152+ lang = _get_moderator_lang (update , moderator )
153+ if moderator is None :
154+ await update .callback_query .answer (_t ("moderator.meme_source.only_moderators_status" , lang ))
139155 return
140156
141157 try :
142158 meme_source_id , status = parse_meme_source_status_callback_data (update .callback_query .data )
143159 except (IndexError , KeyError , ValueError ):
144- await update .callback_query .answer ("Invalid meme source status action" )
160+ await update .callback_query .answer (_t ( "moderator.meme_source.invalid_status_action" , lang ) )
145161 return
146162
147163 try :
@@ -154,24 +170,37 @@ async def handle_meme_source_change_status(
154170 trigger_parse = False ,
155171 )
156172 except MemeSourceNotFoundError :
157- await update .callback_query .answer (f"Meme source { meme_source_id } not found" )
173+ await update .callback_query .answer (
174+ _t ("moderator.meme_source.not_found_by_id" , lang , source_id = meme_source_id )
175+ )
158176 return
159177 except ValueError as e :
160178 await update .callback_query .answer (str (e )[:180 ])
161179 return
162180
163181 if result ["unsnoozed_count" ]:
164182 await update .effective_chat .send_message (
165- f"Unsnoozed { result ['unsnoozed_count' ]} memes of { meme_source_id } "
183+ _t (
184+ "moderator.meme_source.unsnoozed_memes" ,
185+ lang ,
186+ count = result ["unsnoozed_count" ],
187+ source_id = meme_source_id ,
188+ )
166189 )
167190
168191 await log (
169192 f"ℹ️ MemeSource ${ meme_source_id } : set_status={ status } (by { user_id } )" ,
170193 context .bot ,
171194 )
172195
173- await update .callback_query .answer (f"Meme source status is { status } now" )
174- await meme_source_admin_pipeline (result ["source" ], update )
196+ await update .callback_query .answer (
197+ _t (
198+ "moderator.meme_source.status_updated" ,
199+ lang ,
200+ status = _status_label (status , lang ),
201+ )
202+ )
203+ await meme_source_admin_pipeline (result ["source" ], update , lang )
175204
176205 meme_source = result ["source" ]
177206 if status == MemeSourceStatus .PARSING_ENABLED : # trigger parsing
@@ -183,57 +212,127 @@ async def handle_meme_source_change_status(
183212
184213 if result ["snoozed_count" ]:
185214 await update .effective_chat .send_message (
186- f"Snoozed { result ['snoozed_count' ]} memes of { meme_source_id } "
215+ _t (
216+ "moderator.meme_source.snoozed_memes" ,
217+ lang ,
218+ count = result ["snoozed_count" ],
219+ source_id = meme_source_id ,
220+ )
187221 )
188222
189223
190- def _get_meme_source_info (meme_source : dict ) -> str :
191- return f"""
192- id: { meme_source ["id" ]}
193- url: { meme_source ["url" ]}
194- type: { meme_source ["type" ]}
195- language: { meme_source ["language_code" ]}
196- added by: { meme_source ["added_by" ]}
197- <b>status</b>: { meme_source ["status" ]}
198- """
224+ def _get_moderator_lang (
225+ update : Update ,
226+ moderator_info : dict [str , Any ] | None = None ,
227+ ) -> str | None :
228+ if moderator_info and moderator_info .get ("interface_lang" ):
229+ return str (moderator_info ["interface_lang" ])
230+ if update .effective_user and getattr (update .effective_user , "language_code" , None ):
231+ return update .effective_user .language_code
232+ return "ru"
233+
234+
235+ def _html (value : object ) -> str :
236+ if value is None :
237+ return "—"
238+ return html_escape (str (value ), quote = False )
239+
240+
241+ def _source_type_label (source_type : object ) -> str :
242+ value = getattr (source_type , "value" , source_type )
243+ if value == MemeSourceType .TELEGRAM .value :
244+ return "Telegram"
245+ if value == MemeSourceType .INSTAGRAM .value :
246+ return "Instagram"
247+ if value == MemeSourceType .VK .value :
248+ return "VK"
249+ return _html (value )
250+
251+
252+ def _status_label (status : object , lang : str | None ) -> str :
253+ value = getattr (status , "value" , status )
254+ try :
255+ return localizer .t (f"moderator.meme_source.status.{ value } " , lang )
256+ except KeyError :
257+ return _html (value )
258+
199259
200- # Column("nlikes", Integer, nullable=False, server_default="0"),
201- # Column("ndislikes", Integer, nullable=False, server_default="0"),
202- # Column("nmemes_sent_events", Integer, nullable=False, server_default="0"),
203- # Column("nmemes_parsed", Integer, nullable=False, server_default="0"),
204- # Column("nmemes_sent", Integer, nullable=False, server_default="0"),
205- # Column("latest_meme_age", Integer, nullable=False, server_default="0"),
260+ def _format_int (value : object ) -> str :
261+ try :
262+ return f"{ int (value ):,} " .replace ("," , " " )
263+ except (TypeError , ValueError ):
264+ return "—"
206265
207266
208- def _get_meme_source_stats_info (meme_source_stats : dict ) -> str :
209- return f"""
210- likes: { meme_source_stats ["nlikes" ]}
211- dislikes: { meme_source_stats ["ndislikes" ]}
212- memes sent events: { meme_source_stats ["nmemes_sent_events" ]}
213- memes parsed: { meme_source_stats ["nmemes_parsed" ]}
214- memes sent: { meme_source_stats ["nmemes_sent" ]}
215- latest meme age: { meme_source_stats ["latest_meme_age" ]}
216- """
267+ def _format_latest_age (value : object , lang : str | None ) -> str :
268+ try :
269+ days = int (value )
270+ except (TypeError , ValueError ):
271+ return "—"
272+
273+ if days <= 0 :
274+ return localizer .t ("moderator.meme_source.today" , lang )
275+
276+ return _t ("moderator.meme_source.days_short" , lang , days = days )
277+
278+
279+ def _get_meme_source_info (meme_source : dict , lang : str | None ) -> str :
280+ source_type = _source_type_label (meme_source ["type" ])
281+ language = _html (meme_source ["language_code" ])
282+ added_by = _html (meme_source ["added_by" ])
283+ status = _status_label (meme_source ["status" ], lang )
284+
285+ return _t (
286+ "moderator.meme_source.card" ,
287+ lang ,
288+ id = _html (meme_source ["id" ]),
289+ type = source_type ,
290+ language = language ,
291+ url = _html (meme_source ["url" ]),
292+ status = status ,
293+ added_by = added_by ,
294+ )
295+
296+
297+ def _get_meme_source_stats_info (meme_source_stats : dict , lang : str | None ) -> str :
298+ return _t (
299+ "moderator.meme_source.stats" ,
300+ lang ,
301+ likes = _format_int (meme_source_stats ["nlikes" ]),
302+ dislikes = _format_int (meme_source_stats ["ndislikes" ]),
303+ sent_events = _format_int (meme_source_stats ["nmemes_sent_events" ]),
304+ parsed = _format_int (meme_source_stats ["nmemes_parsed" ]),
305+ sent = _format_int (meme_source_stats ["nmemes_sent" ]),
306+ latest_age = _format_latest_age (meme_source_stats ["latest_meme_age" ], lang ),
307+ )
217308
218309
219310async def meme_source_admin_pipeline (
220311 meme_source : dict ,
221312 update : Update ,
313+ lang : str | None = None ,
222314) -> Message :
223- ms_info = _get_meme_source_info (meme_source )
315+ if lang is None :
316+ lang = _get_moderator_lang (update )
317+
318+ ms_info = _get_meme_source_info (meme_source , lang )
224319 ms_stats = await get_meme_source_stats_by_id (meme_source ["id" ])
225320 if ms_stats :
226- ms_info += _get_meme_source_stats_info (ms_stats )
321+ ms_info += " \n \n " + _get_meme_source_stats_info (ms_stats , lang )
227322
228323 if meme_source ["language_code" ] is None :
229324 return await send_or_edit (
230325 update ,
231- text = f""" { ms_info } \n Please select a language for { meme_source [ "url" ] } "" " ,
326+ text = f"{ ms_info } \n \n { localizer . t ( 'moderator. meme_source.select_language' , lang ) } " ,
232327 reply_markup = meme_source_language_selection_keyboard (meme_source_id = meme_source ["id" ]),
233328 )
234329
235330 return await send_or_edit (
236331 update ,
237332 text = ms_info ,
238- reply_markup = meme_source_change_status_keyboard (meme_source ["id" ], meme_source ["status" ]),
333+ reply_markup = meme_source_change_status_keyboard (
334+ meme_source ["id" ],
335+ meme_source ["status" ],
336+ lang ,
337+ ),
239338 )
0 commit comments