feat: Implement initial web frontend, comprehensive database management, and a bot-based weekly reward system.
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
except Exception:
|
||||
ZoneInfo = None # type: ignore[assignment]
|
||||
|
||||
|
||||
def compute_target_week_key(
|
||||
now_local: datetime,
|
||||
settle_weekday: int,
|
||||
settle_hour: int,
|
||||
settle_minute: int,
|
||||
) -> str:
|
||||
week_start = (now_local - timedelta(days=now_local.isoweekday() - 1)).replace(
|
||||
hour=0,
|
||||
minute=0,
|
||||
second=0,
|
||||
microsecond=0,
|
||||
)
|
||||
settle_dt = week_start + timedelta(days=settle_weekday - 1)
|
||||
settle_dt = settle_dt.replace(
|
||||
hour=settle_hour,
|
||||
minute=settle_minute,
|
||||
second=0,
|
||||
microsecond=0,
|
||||
)
|
||||
ref = now_local - timedelta(days=7 if now_local >= settle_dt else 14)
|
||||
iso_year, iso_week, _ = ref.isocalendar()
|
||||
return f"{iso_year}-W{iso_week:02d}"
|
||||
|
||||
|
||||
def resolve_tz(tz_name: str):
|
||||
if ZoneInfo is None:
|
||||
return timezone(timedelta(hours=8)), "UTC+08:00"
|
||||
try:
|
||||
return ZoneInfo(tz_name), tz_name
|
||||
except Exception:
|
||||
return ZoneInfo("Asia/Shanghai"), "Asia/Shanghai"
|
||||
|
||||
|
||||
def get_candidates(conn: sqlite3.Connection, week_key: str, limit: int):
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT
|
||||
u.telegram_id,
|
||||
u.username,
|
||||
lower(trim(COALESCE(u.supabase_user_id, ''))) AS supabase_user_id,
|
||||
COALESCE(u.points, 0) AS points,
|
||||
COALESCE(u.message_count, 0) AS message_count,
|
||||
COALESCE(a.points,
|
||||
CASE
|
||||
WHEN u.weekly_points_week = ? THEN COALESCE(u.weekly_points, 0)
|
||||
ELSE 0
|
||||
END
|
||||
) AS weekly_points
|
||||
FROM users u
|
||||
LEFT JOIN weekly_points_archive a
|
||||
ON a.telegram_id = u.telegram_id
|
||||
AND a.week_key = ?
|
||||
) ranked
|
||||
WHERE weekly_points > 0
|
||||
ORDER BY weekly_points DESC, points DESC, message_count DESC, telegram_id ASC
|
||||
LIMIT ?
|
||||
""",
|
||||
(week_key, week_key, max(1, int(limit))),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def get_settle_run(conn: sqlite3.Connection, week_key: str) -> Optional[sqlite3.Row]:
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn.execute(
|
||||
"""
|
||||
SELECT week_key, settled_at, winners_count
|
||||
FROM weekly_reward_runs
|
||||
WHERE week_key = ?
|
||||
LIMIT 1
|
||||
""",
|
||||
(week_key,),
|
||||
).fetchone()
|
||||
|
||||
|
||||
def get_recent_runs(conn: sqlite3.Connection, limit: int = 5):
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT week_key, settled_at, winners_count
|
||||
FROM weekly_reward_runs
|
||||
ORDER BY settled_at DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(max(1, int(limit)),),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def get_recent_payouts(conn: sqlite3.Connection, limit: int = 20):
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT week_key, telegram_id, username, rank, points_bonus, pro_days, pro_granted, pro_error, created_at
|
||||
FROM weekly_reward_payouts
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(max(1, int(limit)),),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Check PolyWeather weekly reward settlement status.")
|
||||
parser.add_argument("--db", default=os.getenv("POLYWEATHER_DB_PATH", "data/polyweather.db"))
|
||||
parser.add_argument("--timezone", default=os.getenv("POLYWEATHER_WEEKLY_REWARD_TIMEZONE", "Asia/Shanghai"))
|
||||
parser.add_argument("--settle-weekday", type=int, default=int(os.getenv("POLYWEATHER_WEEKLY_REWARD_SETTLE_WEEKDAY", "1")))
|
||||
parser.add_argument("--settle-hour", type=int, default=int(os.getenv("POLYWEATHER_WEEKLY_REWARD_SETTLE_HOUR", "0")))
|
||||
parser.add_argument("--settle-minute", type=int, default=int(os.getenv("POLYWEATHER_WEEKLY_REWARD_SETTLE_MINUTE", "5")))
|
||||
parser.add_argument("--top", type=int, default=10)
|
||||
args = parser.parse_args()
|
||||
|
||||
settle_weekday = min(7, max(1, int(args.settle_weekday)))
|
||||
settle_hour = min(23, max(0, int(args.settle_hour)))
|
||||
settle_minute = min(59, max(0, int(args.settle_minute)))
|
||||
tz, tz_label = resolve_tz(str(args.timezone).strip() or "Asia/Shanghai")
|
||||
now_local = datetime.now(tz)
|
||||
week_key = compute_target_week_key(
|
||||
now_local,
|
||||
settle_weekday=settle_weekday,
|
||||
settle_hour=settle_hour,
|
||||
settle_minute=settle_minute,
|
||||
)
|
||||
|
||||
print("=== Weekly Reward Status ===")
|
||||
print(f"db={args.db}")
|
||||
print(f"now_local={now_local.isoformat()}")
|
||||
print(f"timezone={tz_label}")
|
||||
print(f"settle_rule=weekday:{settle_weekday} time:{settle_hour:02d}:{settle_minute:02d}")
|
||||
print(f"target_week={week_key}")
|
||||
|
||||
with sqlite3.connect(args.db) as conn:
|
||||
run = get_settle_run(conn, week_key)
|
||||
print(f"settled={bool(run)}")
|
||||
if run:
|
||||
print(f"settled_at={run['settled_at']} winners_count={run['winners_count']}")
|
||||
|
||||
candidates = get_candidates(conn, week_key, args.top)
|
||||
print(f"candidates={len(candidates)}")
|
||||
for idx, row in enumerate(candidates, start=1):
|
||||
print(
|
||||
f" {idx}. {row.get('username')} "
|
||||
f"(tg={row.get('telegram_id')}, weekly={row.get('weekly_points')}, total={row.get('points')})"
|
||||
)
|
||||
|
||||
print("\nrecent_runs:")
|
||||
runs = get_recent_runs(conn, limit=5)
|
||||
if not runs:
|
||||
print(" (none)")
|
||||
else:
|
||||
for row in runs:
|
||||
print(
|
||||
f" - {row.get('week_key')} settled_at={row.get('settled_at')} "
|
||||
f"winners={row.get('winners_count')}"
|
||||
)
|
||||
|
||||
print("\nrecent_payouts:")
|
||||
payouts = get_recent_payouts(conn, limit=20)
|
||||
if not payouts:
|
||||
print(" (none)")
|
||||
else:
|
||||
for row in payouts:
|
||||
print(
|
||||
f" - week={row.get('week_key')} rank={row.get('rank')} "
|
||||
f"user={row.get('username')} tg={row.get('telegram_id')} "
|
||||
f"+{row.get('points_bonus')}pts +{row.get('pro_days')}dPro "
|
||||
f"pro_granted={row.get('pro_granted')}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
Reference in New Issue
Block a user