@@ -100,46 +100,41 @@ async def add_money(id: int, count: int):
100100 """
101101 id_str = str (id )
102102 data = await _load_json_async (pathes .money )
103-
104103 old = data .get (id_str , 0 )
105104 new_val = max (old + count , 0 )
106105 data [id_str ] = new_val
107-
108106 await _save_json_async (pathes .money , data , indent = True )
109-
110107 logger .info (f"Изменён баланс { id } ({ old } -> { new_val } )" )
111108 return new_val
112109
113110
114- async def check_and_update_withdraw_limit (id : int , amount : int ) -> tuple [bool , int ]:
111+ async def check_and_update_withdraw_limit (
112+ id : int , amount : int
113+ ) -> tuple [bool , int ]:
115114 """
116115 Атомарная проверка и обновление day-limit.
117116 Должна вызываться внутри async with await get_user_lock(id).
118117 """
119118 id_str = str (id )
120119 today = datetime .now ().date ()
121-
122120 data = await _load_json_async (pathes .wdraw )
123-
124121 already_withdrawn = 0
125122 record_date = None
126-
127123 if id_str in data :
128124 try :
129- record_date = datetime .strptime (data [id_str ]["date" ], "%Y-%m-%d" ).date ()
125+ record_date = datetime .strptime (
126+ data [id_str ]["date" ], "%Y-%m-%d"
127+ ).date ()
130128 already_withdrawn = data [id_str ].get ("withdrawn" , 0 )
131129 except KeyError , ValueError :
132130 record_date = None
133-
134131 if record_date != today :
135132 data [id_str ] = {"date" : today .isoformat (), "withdrawn" : amount }
136133 await _save_json_async (pathes .wdraw , data , indent = True )
137134 return True , 64 - amount
138-
139135 remaining = 64 - already_withdrawn
140136 if amount > remaining :
141137 return False , remaining
142-
143138 data [id_str ]["withdrawn" ] = already_withdrawn + amount
144139 await _save_json_async (pathes .wdraw , data , indent = True )
145140 return True , remaining
@@ -149,7 +144,6 @@ async def rollback_withdraw_limit(id: int, amount: int):
149144 """Откатывает лимит назад."""
150145 id_str = str (id )
151146 data = await _load_json_async (pathes .wdraw )
152-
153147 if id_str in data :
154148 current = data [id_str ].get ("withdrawn" , 0 )
155149 data [id_str ]["withdrawn" ] = max (0 , current - amount )
@@ -160,18 +154,14 @@ async def update_shop():
160154 """Обновляет магазин, возвращая новую тему."""
161155 last_theme = (await _load_json_async (pathes .shopc )).get ("theme" )
162156 all_themes = await _load_json_async (pathes .shop )
163-
164157 if not isinstance (all_themes , dict ) or not all_themes :
165158 logger .exception ("Файл shop_all.json пуст или не содержит тем." )
166159 return None
167-
168160 theme_names = list (all_themes .keys ())
169161 if last_theme in theme_names and len (theme_names ) == 1 :
170162 logger .exception ("Нет доступных альтернативных тем в shop_all.json" )
171163 return None
172-
173164 weights = config .cfg .ShopThemeWeights
174-
175165 new_theme = last_theme
176166 attempts = 0
177167 while new_theme == last_theme and attempts < 10 :
@@ -180,21 +170,19 @@ async def update_shop():
180170 if new_theme == last_theme :
181171 others = [t for t in theme_names if t != last_theme ]
182172 new_theme = choice (others ) if others else last_theme
183-
184173 theme_items = all_themes .get (new_theme , {})
185174 if not isinstance (theme_items , dict ):
186175 logger .exception (f"Тема '{ new_theme } ' не содержит предметов" )
187176 return None
188-
189177 item_names = list (theme_items .keys ())
190178 if len (item_names ) < 5 :
191179 logger .exception (
192180 f"В теме '{ new_theme } ' недостаточно предметов (минимум 5, найдено { len (item_names )} )" ,
193181 )
194182 return None
195-
196- selected_items = sample (item_names , 5 ) if len (item_names ) > 5 else item_names [:5 ]
197-
183+ selected_items = (
184+ sample (item_names , 5 ) if len (item_names ) > 5 else item_names [:5 ]
185+ )
198186 current_shop = {"theme" : new_theme }
199187 for item in selected_items :
200188 item_data = theme_items [item ].copy ()
@@ -206,9 +194,10 @@ async def update_shop():
206194 ):
207195 item_data ["price" ] = randint (price [0 ], price [1 ])
208196 elif not isinstance (price , (int , float )):
209- logger .exception (f"Некорректный формат цены для предмета '{ item } ': { price } " )
197+ logger .exception (
198+ f"Некорректный формат цены для предмета '{ item } ': { price } "
199+ )
210200 current_shop [item ] = item_data
211-
212201 await _save_json_async (pathes .shopc , current_shop , indent = True )
213202 return new_theme
214203
@@ -285,7 +274,9 @@ async def add(self):
285274
286275 async def get_all (self = False ):
287276 data = await _load_json_async (pathes .crocostat )
288- return dict (sorted (data .items (), key = lambda item : item [1 ], reverse = True ))
277+ return dict (
278+ sorted (data .items (), key = lambda item : item [1 ], reverse = True )
279+ )
289280
290281
291282class Nicks :
@@ -324,20 +315,19 @@ async def link(self):
324315
325316
326317class Statistic :
327- def __init__ (self , days = 1 ):
318+ def __init__ (self , days = 1 , nick = None ):
328319 self .days = days
320+ self .nick = nick
329321
330322 async def get (self , nick , all_days = False , data = False ):
331323 filepath = pathes .stats / f"{ nick } .json"
332324 if not filepath .exists ():
333325 stats = {datetime .now ().strftime ("%Y.%m.%d" ): 0 }
334326 await _save_json_async (filepath , stats , sort_keys = True )
335327 return 0
336-
337328 stats = await _load_json_async (filepath )
338329 if all_days :
339330 return sum (stats .values ()) or 0
340-
341331 start_date = datetime .now () - timedelta (days = self .days )
342332 filtered = {
343333 date : value
@@ -355,28 +345,40 @@ async def get_all(self, all_days=False):
355345 try :
356346 nick_stat = await self .get (nick , all_days = all_days )
357347 except Exception :
358- logger .warning (f"Ошибка при получении статистики для игрока { nick } " )
348+ logger .warning (
349+ f"Ошибка при получении статистики для игрока { nick } "
350+ )
359351 continue
360352 else :
361353 if nick_stat > 1 :
362354 data [nick ] = nick_stat
363355 return sorted (data .items (), key = lambda item : item [1 ], reverse = True )
364356
365- async def add (self , date = None ):
357+ async def add (self , nick : str = None , date = None ):
358+ """
359+ Добавляет единицу статистики для игрока.
360+ Args:
361+ nick: Имя игрока. Если не указан, используется self.nick
362+ date: Дата в формате YYYY.MM.DD (опционально)
363+ """
364+ target_nick = nick or self .nick
365+ if not target_nick :
366+ msg = "Nickname must be provided either as argument or via __init__"
367+ raise ValueError (
368+ msg
369+ )
370+ if not formatter .is_valid_mc_nick (target_nick ):
371+ msg = f"Invalid nickname: { target_nick } "
372+ raise ValueError (msg )
366373 now = date or datetime .now ().strftime ("%Y.%m.%d" )
367- raw_name = str (self )
368- safe_name = Path (raw_name ).name
369- if safe_name != raw_name or not safe_name :
370- logger .warning (f"Некорректное имя статистики: { raw_name !r} " )
371- return
372- filepath = pathes .stats / f"{ safe_name } .json"
374+ filepath = pathes .stats / f"{ target_nick } .json"
373375 base_stats = pathes .stats .resolve ()
374376 resolved_filepath = filepath .resolve ()
375377 try :
376378 resolved_filepath .relative_to (base_stats )
377379 except ValueError :
378380 logger .warning (
379- f"Заблокирован выход за пределы каталога статистики: { raw_name !r} "
381+ f"Заблокирован выход за пределы каталога статистики: { target_nick !r} "
380382 )
381383 return
382384 try :
@@ -399,10 +401,8 @@ async def get_raw(self) -> dict[str, int]:
399401 except Exception :
400402 logger .error (f"Ошибка при чтении файла - { json_file .name } " )
401403 continue
402-
403404 if self .days <= 0 :
404405 return dict (sorted (totals .items (), key = lambda item : item [0 ]))
405-
406406 start_date = datetime .now () - timedelta (days = self .days )
407407 return {
408408 date : value
@@ -473,7 +473,9 @@ def is_recognized(self) -> bool:
473473
474474 def change (self , key , value ):
475475 self .all [key ] = value
476- _save_json_sync (pathes .states / f"{ self .name } .json" , self .all , indent = True )
476+ _save_json_sync (
477+ pathes .states / f"{ self .name } .json" , self .all , indent = True
478+ )
477479
478480 def rename (self , new_name : str ):
479481 new_path = pathes .states / f"{ new_name } .json"
@@ -794,7 +796,9 @@ def rem_player(self, player_id: int):
794796
795797 def who_answer (self ) -> int | None :
796798 players = self .get_players ()
797- return self .data ["current_game" ]["current_player_id" ] if players else None
799+ return (
800+ self .data ["current_game" ]["current_player_id" ] if players else None
801+ )
798802
799803 def next_answer (self ):
800804 players = self .get_players ()
@@ -842,10 +846,14 @@ def start_game(self):
842846 self .data ["id" ] = (self .data .get ("id" , 0 ) + 1 ) % 10 or 1
843847 self .data ["status" ] = True
844848 self .data ["current_game" ]["last_city" ] = city
845- self .data ["current_game" ]["current_player_id" ] = choice (self .get_players ())
849+ self .data ["current_game" ]["current_player_id" ] = choice (
850+ self .get_players ()
851+ )
846852 self .logger (f"Запущена игра Города. Начинается с города { city } " )
847853 self .logger (f"Игроки: { self .get_players ()} " )
848- self .logger (f"Отвечает: { self .data ['current_game' ]['current_player_id' ]} " )
854+ self .logger (
855+ f"Отвечает: { self .data ['current_game' ]['current_player_id' ]} "
856+ )
849857 self ._save_data ()
850858 return self .data
851859
@@ -858,7 +866,9 @@ def answer(self, id: str, city: str):
858866 if str (id ) != str (self .data ["current_game" ]["current_player_id" ]):
859867 self .logger (f"{ id } сейчас не должен отвечать" )
860868 return 2
861- valid_cities = set ((pathes .chk_city ).read_text (encoding = "utf8" ).splitlines ())
869+ valid_cities = set (
870+ (pathes .chk_city ).read_text (encoding = "utf8" ).splitlines ()
871+ )
862872 if city not in valid_cities :
863873 self .logger (f"{ id } ответил неизвестным городом" )
864874 return 1
@@ -872,7 +882,9 @@ def answer(self, id: str, city: str):
872882 self .logger (f"{ id } ответил городом, который был" )
873883 return 5
874884 self .data ["current_game" ]["last_city" ] = city
875- self .data ["statistics" ][str (id )] = self .data ["statistics" ].get (str (id ), 0 ) + 1
885+ self .data ["statistics" ][str (id )] = (
886+ self .data ["statistics" ].get (str (id ), 0 ) + 1
887+ )
876888 self .data ["current_game" ]["cities" ].append (city )
877889 self .next_answer ()
878890 self ._save_data ()
@@ -953,7 +965,9 @@ async def get_crocodile_word() -> str:
953965 return choice (list (words ))
954966
955967
956- async def add_pending_hint (user_id : int | str , hint_string : str , word : str ) -> int :
968+ async def add_pending_hint (
969+ user_id : int | str , hint_string : str , word : str
970+ ) -> int :
957971 data = await _load_json_async (pathes .pending_hints )
958972 pending_id = max ((int (k ) for k in data ), default = 0 ) + 1
959973 data [str (pending_id )] = {
@@ -1020,7 +1034,9 @@ class Item(TypedDict):
10201034 price : int
10211035
10221036
1023- async def add_item (id : str , author_id : int , item : str , count : int , price : int ) -> None :
1037+ async def add_item (
1038+ id : str , author_id : int , item : str , count : int , price : int
1039+ ) -> None :
10241040 """Добавляет новый товар по ID. Перезаписывает, если уже существует."""
10251041 data = await _load_json_async (pathes .items )
10261042 data [str (id )] = {
@@ -1106,7 +1122,6 @@ async def stop_game(self):
11061122 if isinstance (current , dict ):
11071123 word = current .get ("word" )
11081124 await self .set_current (0 )
1109-
11101125 await self .clear_bets ()
11111126 return word
11121127
@@ -1149,15 +1164,13 @@ async def reveal_on_guess(self, guess: str):
11491164 if i < len (guess ) and guess [i ] == ch and mask [i ] == "_" :
11501165 mask [i ] = ch
11511166 changed = True
1152-
11531167 new_mask_str = "" .join (mask )
11541168 finished = False
11551169 if new_mask_str == word :
11561170 if len (mask ) > 0 :
11571171 mask [randint (0 , len (mask ) - 1 )] = "_"
11581172 new_mask_str = "" .join (mask )
11591173 finished = True
1160-
11611174 current ["unsec" ] = new_mask_str
11621175 await self .set_current (current )
11631176 return changed , new_mask_str , finished
@@ -1172,7 +1185,6 @@ async def guess_word(self, user_id: int, guess: str):
11721185 word = current .get ("word" )
11731186 if str (guess ).strip ().lower () == str (word ).strip ().lower ():
11741187 bets = self .get_bets ()
1175-
11761188 await self .set_current (0 )
11771189 await self .clear_bets ()
11781190 return {"win" : True , "word" : word , "bets" : bets }
@@ -1214,11 +1226,9 @@ async def add(self, id: str, topic_id: str) -> None:
12141226 id = self .idconv (id )
12151227 topic_id = self .idconv (topic_id )
12161228 self .data = await _load_json_async (self .data_file )
1217-
12181229 if id not in self .data :
12191230 self .data [id ] = []
12201231 self .data [id ].append (topic_id )
1221-
12221232 return await _save_json_async (self .data_file , self .data , indent = True )
12231233
12241234 async def remove (self , id : str , topic_id : str ) -> bool :
0 commit comments