Files
chatrebot_aireply_plug/scripts/mc_players.py
Claw e1fbd31c64 完整 AgentSkill:词库外部化 + 全部脚本/Skill 脱敏上传
- 高危词库从 process.py 抽到 config.toml [security] section
- process.py 改为 _get_high_risk_words() 动态加载(配置优先)
- 新增 15 个 QQ 操作脚本(已脱敏:QQ号/IP/Token → 占位符)
- 新增 8 个 SKILL.md(qq-messenger/management/resolver/napcat-extras 等)
- 新增 SKILL.md 入口(完整部署方案文档)
- 保留 SDK 框架(plugin_modules.py, file_store_api.py, package.py 等)
2026-05-04 16:23:59 +08:00

211 lines
7.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""获取Minecraft玩家活动信息JSON输出"""
import sys, os, re, json
from datetime import datetime
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from mc_config import MC_SERVER_DIR, PLAYTIME_DB
def get_recent_events(limit=5):
log_path = os.path.join(MC_SERVER_DIR, "logs", "latest.log")
if not os.path.exists(log_path):
return []
events = []
patterns = [
(re.compile(r'(\w+)\s+joined\s+the\s+game'), 'join'),
(re.compile(r'(\w+)\s+left\s+the\s+game'), 'leave'),
]
skip = {"Server", "RCON", "Thread", "Generic", "World"}
try:
with open(log_path, "r", errors="ignore") as f:
lines = f.readlines()
for line in lines[-5000:]:
for pattern, etype in patterns:
m = pattern.search(line)
if m:
name = m.group(1)
if name in skip:
continue
events.append({"type": etype, "player": name})
break
seen = set()
unique = []
for e in reversed(events):
key = (e["player"], e["type"])
if key not in seen:
seen.add(key)
unique.append(e)
if len(unique) >= limit:
break
return list(reversed(unique))
except Exception:
return []
def get_playtime_db():
if os.path.exists(PLAYTIME_DB):
try:
with open(PLAYTIME_DB, "r") as f:
return json.load(f)
except Exception:
pass
return {"players": {}, "last_log_pos": 0}
def save_playtime_db(db):
try:
with open(PLAYTIME_DB, "w") as f:
json.dump(db, f, indent=2, ensure_ascii=False)
except Exception:
pass
def update_playtime():
db = get_playtime_db()
log_path = os.path.join(MC_SERVER_DIR, "logs", "latest.log")
if not os.path.exists(log_path):
return db
try:
with open(log_path, "rb") as f:
f.seek(0, 2)
size = f.tell()
start = min(db.get("last_log_pos", 0), size)
if start > size:
start = 0
with open(log_path, "r", errors="ignore") as f:
f.seek(start)
lines = f.readlines()
now = datetime.now()
join_p = re.compile(r'joined the game')
leave_p = re.compile(r'left the game')
name_p = re.compile(r'\]:\s+(\w+)\s+(?:joined|left)')
for line in lines:
m = name_p.search(line)
if not m:
continue
player = m.group(1)
if join_p.search(line):
if player not in db["players"]:
db["players"][player] = {"total_seconds": 0, "last_join": None}
db["players"][player]["last_join"] = now.isoformat()
elif leave_p.search(line):
if player in db["players"] and db["players"][player].get("last_join"):
join_time = datetime.fromisoformat(db["players"][player]["last_join"])
sec = (now - join_time).total_seconds()
if 0 < sec < 86400:
db["players"][player]["total_seconds"] += sec
db["players"][player]["last_join"] = None
db["last_log_pos"] = size
save_playtime_db(db)
except Exception:
pass
return db
def format_time(seconds):
h = seconds // 3600
m = (seconds % 3600) // 60
if h >= 24:
d = h // 24
h = h % 24
return f"{d}{h}小时{m}"
elif h > 0:
return f"{h}小时{m}"
return f"{m}分钟"
def get_playtime_leaderboard(top_n=10):
db = update_playtime()
players = db.get("players", {})
lb = []
for name, data in players.items():
total = data.get("total_seconds", 0)
if data.get("last_join"):
try:
jt = datetime.fromisoformat(data["last_join"])
s = (datetime.now() - jt).total_seconds()
if 0 < s < 86400:
total += s
except Exception:
pass
if total > 0:
lb.append({"name": name, "seconds": int(total), "formatted": format_time(int(total))})
lb.sort(key=lambda x: -x["seconds"])
return lb[:top_n]
def _load_uuid_to_name():
"""从 usercache.json 加载 UUID->名字映射,回退 ops.json/whitelist.json"""
mapping = {}
# 主源usercache.json包含所有登录过的玩家
for src_file in ["usercache.json", "ops.json", "whitelist.json"]:
path = os.path.join(MC_SERVER_DIR, src_file)
try:
with open(path, "r") as f:
for entry in json.load(f):
if 'uuid' in entry and 'name' in entry:
mapping[entry['uuid']] = entry['name']
except Exception:
pass
return mapping
def get_death_leaderboard(top_n=10):
uuid_to_name = _load_uuid_to_name()
stats_dir = os.path.join(MC_SERVER_DIR, "world", "stats")
if not os.path.exists(stats_dir):
return []
deaths = {}
for fname in os.listdir(stats_dir):
if not fname.endswith('.json'):
continue
uuid = fname.replace('.json', '')
fpath = os.path.join(stats_dir, fname)
try:
with open(fpath, "r") as f:
data = json.load(f)
dc = data.get('stats', {}).get('minecraft:custom', {}).get('minecraft:deaths', 0)
if dc > 0:
name = uuid_to_name.get(uuid, uuid[:8])
deaths[name] = dc
except Exception:
continue
return [{"name": n, "deaths": d} for n, d in sorted(deaths.items(), key=lambda x: -x[1])[:top_n]]
def get_achievement_leaderboard(top_n=10):
uuid_to_name = _load_uuid_to_name()
adv_dir = os.path.join(MC_SERVER_DIR, "world", "advancements")
if not os.path.exists(adv_dir):
return []
ach = {}
for fname in os.listdir(adv_dir):
if not fname.endswith('.json'):
continue
uuid = fname.replace('.json', '')
fpath = os.path.join(adv_dir, fname)
try:
with open(fpath, "r") as f:
data = json.load(f)
name = uuid_to_name.get(uuid)
if not name:
name = uuid[:8] # 名字查不到就用 UUID 前缀
done = 0
for k, v in data.items():
if not isinstance(v, dict):
continue
if 'recipes/' in k or ':recipes/' in k:
continue
if v.get('done'):
done += 1
if done > 0:
ach[name] = done
except Exception:
continue
return [{"name": n, "achievements": a} for n, a in sorted(ach.items(), key=lambda x: -x[1])[:top_n]]
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--top", type=int, default=10)
args = parser.parse_args()
result = {
"recent_events": get_recent_events(5),
"playtime": get_playtime_leaderboard(args.top),
"deaths": get_death_leaderboard(args.top),
"achievements": get_achievement_leaderboard(args.top),
}
print(json.dumps(result, ensure_ascii=False, indent=2))