Skip to content

Commit ead8011

Browse files
committed
Фиксы статистики
1 parent c624edd commit ead8011

2 files changed

Lines changed: 63 additions & 53 deletions

File tree

modules/db.py

Lines changed: 62 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -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

291282
class Nicks:
@@ -324,20 +315,19 @@ async def link(self):
324315

325316

326317
class 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:

modules/webhooks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ async def minecraft(request: aiohttp.web.Request):
9393
nick = request.query.get("nick")
9494
if formatter.is_valid_mc_nick(nick) is False:
9595
return aiohttp.web.Response(text="Nick is not valid", status=406)
96-
await db.Statistic.add(nick)
96+
await db.Statistic().add(nick)
9797
logger.debug(f"+ соо. от {nick}")
9898
return aiohttp.web.Response(text="ok")
9999

0 commit comments

Comments
 (0)