144 lines
6.0 KiB
Python
144 lines
6.0 KiB
Python
from datetime import datetime, timedelta, timezone
|
||
from zoneinfo import ZoneInfo
|
||
from app.db.database import users_collection, db
|
||
|
||
coins_sessions_collection = db.coins_sessions
|
||
|
||
TZ = ZoneInfo("Asia/Yekaterinburg")
|
||
|
||
def _day_bounds_utc(now_utc: datetime):
|
||
now_local = now_utc.astimezone(TZ)
|
||
start_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0)
|
||
start_utc = start_local.astimezone(timezone.utc)
|
||
next_utc = (start_local + timedelta(days=1)).astimezone(timezone.utc)
|
||
return now_local.date(), start_utc, next_utc, start_local
|
||
|
||
class DailyRewardService:
|
||
async def claim_daily(self, username: str) -> dict:
|
||
now_utc = datetime.now(timezone.utc)
|
||
today_local, start_today_utc, start_tomorrow_utc, _ = _day_bounds_utc(now_utc)
|
||
|
||
user = await users_collection.find_one({"username": username})
|
||
if not user:
|
||
return {"claimed": False, "reason": "user_not_found"}
|
||
|
||
was_online_today = await coins_sessions_collection.find_one(
|
||
{
|
||
"player_name": username,
|
||
"update_type": "coins_update",
|
||
"timestamp": {
|
||
"$gte": start_today_utc.replace(tzinfo=None),
|
||
"$lt": start_tomorrow_utc.replace(tzinfo=None),
|
||
},
|
||
}
|
||
)
|
||
|
||
if not was_online_today:
|
||
return {
|
||
"claimed": False,
|
||
"reason": "not_online_today",
|
||
"message": "Вы должны зайти на сервер сегодня, чтобы получить ежедневную награду",
|
||
}
|
||
|
||
last_claim_at = user.get("daily_last_claim_at") # ожидаем datetime (лучше хранить UTC)
|
||
last_local_day = last_claim_at.replace(tzinfo=timezone.utc).astimezone(TZ).date() if last_claim_at else None
|
||
|
||
if last_local_day == today_local:
|
||
return {"claimed": False, "reason": "already_claimed_today", "streak": user.get("daily_streak", 0)}
|
||
|
||
yesterday_local = today_local - timedelta(days=1)
|
||
prev_streak = int(user.get("daily_streak", 0) or 0)
|
||
new_streak = (prev_streak + 1) if (last_local_day == yesterday_local) else 1
|
||
|
||
# твоя новая формула: 10, 20, 30 ... до 50 (кап)
|
||
reward = min(10 + (new_streak - 1) * 10, 50)
|
||
|
||
result = await users_collection.update_one(
|
||
{
|
||
"username": username,
|
||
"$or": [
|
||
{"daily_last_claim_at": {"$exists": False}},
|
||
{"daily_last_claim_at": {"$lt": start_today_utc.replace(tzinfo=None)}},
|
||
],
|
||
},
|
||
{
|
||
"$inc": {"coins": reward},
|
||
"$set": {"daily_last_claim_at": now_utc.replace(tzinfo=None), "daily_streak": new_streak},
|
||
},
|
||
)
|
||
|
||
if result.modified_count == 0:
|
||
user2 = await users_collection.find_one({"username": username})
|
||
return {"claimed": False, "reason": "already_claimed_today", "streak": user2.get("daily_streak", 0)}
|
||
|
||
await coins_sessions_collection.insert_one({
|
||
"player_name": username,
|
||
"update_type": "daily_login",
|
||
"timestamp": now_utc.replace(tzinfo=None),
|
||
"coins_added": reward,
|
||
"streak": new_streak,
|
||
})
|
||
|
||
return {"claimed": True, "coins_added": reward, "streak": new_streak}
|
||
|
||
async def get_status(self, username: str) -> dict:
|
||
now_utc = datetime.now(timezone.utc)
|
||
today_local, start_today_utc, start_tomorrow_utc, start_today_local = _day_bounds_utc(now_utc)
|
||
|
||
user = await users_collection.find_one({"username": username})
|
||
if not user:
|
||
return {"ok": False, "reason": "user_not_found"}
|
||
|
||
was_online_today = await coins_sessions_collection.find_one({
|
||
"player_name": username,
|
||
"update_type": "coins_update",
|
||
"timestamp": {
|
||
"$gte": start_today_utc.replace(tzinfo=None),
|
||
"$lt": start_tomorrow_utc.replace(tzinfo=None),
|
||
},
|
||
})
|
||
|
||
last_claim_at = user.get("daily_last_claim_at")
|
||
last_local_day = last_claim_at.replace(tzinfo=timezone.utc).astimezone(TZ).date() if last_claim_at else None
|
||
|
||
can_claim = (last_local_day != today_local) and bool(was_online_today)
|
||
|
||
seconds_to_next = 0 if can_claim else int((start_tomorrow_utc - now_utc).total_seconds())
|
||
if seconds_to_next < 0:
|
||
seconds_to_next = 0
|
||
|
||
return {
|
||
"ok": True,
|
||
"can_claim": can_claim,
|
||
"was_online_today": bool(was_online_today),
|
||
"seconds_to_next": seconds_to_next,
|
||
"next_claim_at_utc": start_tomorrow_utc.isoformat().replace("+00:00", "Z"),
|
||
"next_claim_at_local": (start_today_local + timedelta(days=1)).isoformat(),
|
||
"streak": int(user.get("daily_streak", 0) or 0),
|
||
}
|
||
|
||
async def get_claim_days(self, username: str, limit: int = 60) -> dict:
|
||
# Берём последние N записей daily_login и превращаем в список уникальных дней по ЕКБ
|
||
cursor = coins_sessions_collection.find(
|
||
{"player_name": username, "update_type": "daily_login"},
|
||
{"timestamp": 1, "_id": 0},
|
||
).sort("timestamp", -1).limit(limit)
|
||
|
||
days = []
|
||
seen = set()
|
||
|
||
async for doc in cursor:
|
||
ts = doc.get("timestamp")
|
||
if not ts:
|
||
continue
|
||
|
||
# У тебя timestamp в Mongo — naive UTC (now_utc.replace(tzinfo=None)) :contentReference[oaicite:1]{index=1}
|
||
ts_utc = ts.replace(tzinfo=timezone.utc)
|
||
day_local = ts_utc.astimezone(TZ).date().isoformat() # YYYY-MM-DD по ЕКБ
|
||
|
||
if day_local not in seen:
|
||
seen.add(day_local)
|
||
days.append(day_local)
|
||
|
||
days.reverse() # чтобы было по возрастанию (старые → новые), если надо
|
||
return {"ok": True, "days": days, "count": len(days)} |