endstone-qqsync-plugin 0.0.7__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- endstone_qqsync_plugin/__init__.py +3 -0
- endstone_qqsync_plugin/core/__init__.py +18 -0
- endstone_qqsync_plugin/core/config_manager.py +224 -0
- endstone_qqsync_plugin/core/data_manager.py +511 -0
- endstone_qqsync_plugin/core/event_handlers.py +552 -0
- endstone_qqsync_plugin/core/permission_manager.py +331 -0
- endstone_qqsync_plugin/core/verification_manager.py +718 -0
- endstone_qqsync_plugin/lib/websockets/__init__.py +236 -0
- endstone_qqsync_plugin/lib/websockets/__main__.py +5 -0
- endstone_qqsync_plugin/lib/websockets/asyncio/__init__.py +0 -0
- endstone_qqsync_plugin/lib/websockets/asyncio/async_timeout.py +282 -0
- endstone_qqsync_plugin/lib/websockets/asyncio/client.py +820 -0
- endstone_qqsync_plugin/lib/websockets/asyncio/compatibility.py +30 -0
- endstone_qqsync_plugin/lib/websockets/asyncio/connection.py +1237 -0
- endstone_qqsync_plugin/lib/websockets/asyncio/messages.py +314 -0
- endstone_qqsync_plugin/lib/websockets/asyncio/router.py +198 -0
- endstone_qqsync_plugin/lib/websockets/asyncio/server.py +981 -0
- endstone_qqsync_plugin/lib/websockets/auth.py +18 -0
- endstone_qqsync_plugin/lib/websockets/cli.py +178 -0
- endstone_qqsync_plugin/lib/websockets/client.py +389 -0
- endstone_qqsync_plugin/lib/websockets/connection.py +12 -0
- endstone_qqsync_plugin/lib/websockets/datastructures.py +187 -0
- endstone_qqsync_plugin/lib/websockets/exceptions.py +473 -0
- endstone_qqsync_plugin/lib/websockets/extensions/__init__.py +4 -0
- endstone_qqsync_plugin/lib/websockets/extensions/base.py +123 -0
- endstone_qqsync_plugin/lib/websockets/extensions/permessage_deflate.py +697 -0
- endstone_qqsync_plugin/lib/websockets/frames.py +430 -0
- endstone_qqsync_plugin/lib/websockets/headers.py +586 -0
- endstone_qqsync_plugin/lib/websockets/http.py +20 -0
- endstone_qqsync_plugin/lib/websockets/http11.py +427 -0
- endstone_qqsync_plugin/lib/websockets/imports.py +100 -0
- endstone_qqsync_plugin/lib/websockets/legacy/__init__.py +11 -0
- endstone_qqsync_plugin/lib/websockets/legacy/auth.py +190 -0
- endstone_qqsync_plugin/lib/websockets/legacy/client.py +705 -0
- endstone_qqsync_plugin/lib/websockets/legacy/exceptions.py +71 -0
- endstone_qqsync_plugin/lib/websockets/legacy/framing.py +225 -0
- endstone_qqsync_plugin/lib/websockets/legacy/handshake.py +158 -0
- endstone_qqsync_plugin/lib/websockets/legacy/http.py +201 -0
- endstone_qqsync_plugin/lib/websockets/legacy/protocol.py +1641 -0
- endstone_qqsync_plugin/lib/websockets/legacy/server.py +1191 -0
- endstone_qqsync_plugin/lib/websockets/protocol.py +758 -0
- endstone_qqsync_plugin/lib/websockets/py.typed +0 -0
- endstone_qqsync_plugin/lib/websockets/server.py +587 -0
- endstone_qqsync_plugin/lib/websockets/speedups.c +222 -0
- endstone_qqsync_plugin/lib/websockets/speedups.pyi +1 -0
- endstone_qqsync_plugin/lib/websockets/streams.py +151 -0
- endstone_qqsync_plugin/lib/websockets/sync/__init__.py +0 -0
- endstone_qqsync_plugin/lib/websockets/sync/client.py +648 -0
- endstone_qqsync_plugin/lib/websockets/sync/connection.py +1072 -0
- endstone_qqsync_plugin/lib/websockets/sync/messages.py +345 -0
- endstone_qqsync_plugin/lib/websockets/sync/router.py +192 -0
- endstone_qqsync_plugin/lib/websockets/sync/server.py +763 -0
- endstone_qqsync_plugin/lib/websockets/sync/utils.py +45 -0
- endstone_qqsync_plugin/lib/websockets/typing.py +74 -0
- endstone_qqsync_plugin/lib/websockets/uri.py +225 -0
- endstone_qqsync_plugin/lib/websockets/utils.py +51 -0
- endstone_qqsync_plugin/lib/websockets/version.py +92 -0
- endstone_qqsync_plugin/qqsync_plugin.py +356 -0
- endstone_qqsync_plugin/ui/__init__.py +7 -0
- endstone_qqsync_plugin/ui/forms.py +403 -0
- endstone_qqsync_plugin/utils/__init__.py +27 -0
- endstone_qqsync_plugin/utils/helpers.py +45 -0
- endstone_qqsync_plugin/utils/imports.py +31 -0
- endstone_qqsync_plugin/utils/info.py +262 -0
- endstone_qqsync_plugin/utils/message_utils.py +273 -0
- endstone_qqsync_plugin/utils/time_utils.py +347 -0
- endstone_qqsync_plugin/websocket/__init__.py +18 -0
- endstone_qqsync_plugin/websocket/client.py +244 -0
- endstone_qqsync_plugin/websocket/handlers.py +1117 -0
- endstone_qqsync_plugin-0.0.7.dist-info/METADATA +6 -0
- endstone_qqsync_plugin-0.0.7.dist-info/RECORD +73 -0
- endstone_qqsync_plugin-0.0.7.dist-info/WHEEL +5 -0
- endstone_qqsync_plugin-0.0.7.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""
|
|
2
|
+
数据管理模块
|
|
3
|
+
负责QQ绑定数据的存储、查询和管理
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, List, Any, Optional
|
|
10
|
+
from ..utils.time_utils import TimeUtils
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DataManager:
|
|
14
|
+
"""数据管理器"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, data_folder: Path, logger):
|
|
17
|
+
self.data_folder = data_folder
|
|
18
|
+
self.logger = logger
|
|
19
|
+
self.binding_file = data_folder / "data.json"
|
|
20
|
+
self._binding_data: Dict[str, Any] = {}
|
|
21
|
+
self._auto_save_enabled = True
|
|
22
|
+
self._init_binding_data()
|
|
23
|
+
|
|
24
|
+
def _init_binding_data(self):
|
|
25
|
+
"""初始化QQ绑定数据文件"""
|
|
26
|
+
# 如果绑定数据文件不存在,创建空数据
|
|
27
|
+
if not self.binding_file.exists():
|
|
28
|
+
self.binding_file.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
with open(self.binding_file, 'w', encoding='utf-8') as f:
|
|
30
|
+
json.dump({}, f, indent=2, ensure_ascii=False)
|
|
31
|
+
self.logger.info(f"已创建QQ绑定数据文件: {self.binding_file}")
|
|
32
|
+
|
|
33
|
+
# 读取绑定数据
|
|
34
|
+
try:
|
|
35
|
+
with open(self.binding_file, 'r', encoding='utf-8') as f:
|
|
36
|
+
self._binding_data = json.load(f)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
self.logger.error(f"读取QQ绑定数据失败: {e}")
|
|
39
|
+
self._binding_data = {}
|
|
40
|
+
|
|
41
|
+
# 更新旧数据结构兼容性
|
|
42
|
+
self._update_data_structure()
|
|
43
|
+
|
|
44
|
+
from endstone import ColorFormat
|
|
45
|
+
self.logger.info(f"{ColorFormat.AQUA}QQ绑定数据已加载,已绑定玩家: {len(self._binding_data)}{ColorFormat.RESET}")
|
|
46
|
+
|
|
47
|
+
def _update_data_structure(self):
|
|
48
|
+
"""更新数据结构以保持兼容性"""
|
|
49
|
+
data_updated = False
|
|
50
|
+
invalid_bindings = []
|
|
51
|
+
|
|
52
|
+
for player_name, data in self._binding_data.items():
|
|
53
|
+
# 添加缺失的字段
|
|
54
|
+
required_fields = {
|
|
55
|
+
"total_playtime": 0,
|
|
56
|
+
"last_join_time": None,
|
|
57
|
+
"last_quit_time": None,
|
|
58
|
+
"session_count": 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for field, default_value in required_fields.items():
|
|
62
|
+
if field not in data:
|
|
63
|
+
data[field] = default_value
|
|
64
|
+
data_updated = True
|
|
65
|
+
|
|
66
|
+
# 检查并清理无效的QQ绑定
|
|
67
|
+
qq_number = data.get("qq", "")
|
|
68
|
+
bind_time = data.get("bind_time", 0)
|
|
69
|
+
|
|
70
|
+
# 只清理那些QQ为空且没有绑定时间的记录
|
|
71
|
+
if (not qq_number or not qq_number.strip()) and not bind_time:
|
|
72
|
+
invalid_bindings.append(player_name)
|
|
73
|
+
self.logger.warning(f"发现无效绑定:玩家 {player_name} 的QQ号为空且无绑定历史,将被清理")
|
|
74
|
+
|
|
75
|
+
# 清理无效绑定
|
|
76
|
+
for player_name in invalid_bindings:
|
|
77
|
+
del self._binding_data[player_name]
|
|
78
|
+
data_updated = True
|
|
79
|
+
|
|
80
|
+
if invalid_bindings:
|
|
81
|
+
self.logger.info(f"已清理 {len(invalid_bindings)} 个无效的QQ绑定")
|
|
82
|
+
|
|
83
|
+
if data_updated:
|
|
84
|
+
self.save_data()
|
|
85
|
+
if invalid_bindings:
|
|
86
|
+
self.logger.info("已更新绑定数据结构并清理无效绑定")
|
|
87
|
+
else:
|
|
88
|
+
self.logger.info("已更新绑定数据结构以支持在线时间统计")
|
|
89
|
+
|
|
90
|
+
def save_data(self):
|
|
91
|
+
"""保存QQ绑定数据到文件"""
|
|
92
|
+
try:
|
|
93
|
+
# 创建临时文件,避免写入过程中的数据损坏
|
|
94
|
+
temp_file = self.binding_file.with_suffix('.tmp')
|
|
95
|
+
|
|
96
|
+
with open(temp_file, 'w', encoding='utf-8') as f:
|
|
97
|
+
json.dump(self._binding_data, f, indent=2, ensure_ascii=False)
|
|
98
|
+
|
|
99
|
+
# 原子性替换文件
|
|
100
|
+
temp_file.replace(self.binding_file)
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
self.logger.error(f"保存QQ绑定数据失败: {e}")
|
|
104
|
+
# 如果临时文件存在,清理它
|
|
105
|
+
temp_file = self.binding_file.with_suffix('.tmp')
|
|
106
|
+
if temp_file.exists():
|
|
107
|
+
try:
|
|
108
|
+
temp_file.unlink()
|
|
109
|
+
except:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
def trigger_save(self, reason: str = "数据变更"):
|
|
113
|
+
"""触发数据保存"""
|
|
114
|
+
if self._auto_save_enabled:
|
|
115
|
+
self.save_data()
|
|
116
|
+
self.logger.info(f"数据保存: {reason}")
|
|
117
|
+
|
|
118
|
+
# 玩家绑定相关方法
|
|
119
|
+
def is_player_bound(self, player_name: str, player_xuid: str = None) -> bool:
|
|
120
|
+
"""检查玩家是否已绑定QQ"""
|
|
121
|
+
# 如果提供了XUID,优先通过XUID查找
|
|
122
|
+
if player_xuid:
|
|
123
|
+
player_data = self._get_player_by_xuid(player_xuid)
|
|
124
|
+
if player_data:
|
|
125
|
+
qq_number = player_data.get("qq", "")
|
|
126
|
+
return bool(qq_number and qq_number.strip())
|
|
127
|
+
else:
|
|
128
|
+
# 通过XUID没有找到,继续用玩家名查找(向后兼容)
|
|
129
|
+
if player_name in self._binding_data:
|
|
130
|
+
qq_number = self._binding_data[player_name].get("qq", "")
|
|
131
|
+
return bool(qq_number and qq_number.strip())
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
# 仅基于玩家名的检查
|
|
135
|
+
if player_name not in self._binding_data:
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
qq_number = self._binding_data[player_name].get("qq", "")
|
|
139
|
+
return bool(qq_number and qq_number.strip())
|
|
140
|
+
|
|
141
|
+
def _get_player_by_xuid(self, xuid: str) -> Dict[str, Any]:
|
|
142
|
+
"""根据XUID获取玩家绑定信息"""
|
|
143
|
+
for name, data in self._binding_data.items():
|
|
144
|
+
if data.get("xuid") == xuid:
|
|
145
|
+
return data
|
|
146
|
+
return {}
|
|
147
|
+
|
|
148
|
+
def get_player_qq(self, player_name: str) -> str:
|
|
149
|
+
"""获取玩家绑定的QQ号"""
|
|
150
|
+
return self._binding_data.get(player_name, {}).get("qq", "")
|
|
151
|
+
|
|
152
|
+
def get_qq_player(self, qq_number: str) -> str:
|
|
153
|
+
"""根据QQ号获取绑定的玩家名"""
|
|
154
|
+
for name, data in self._binding_data.items():
|
|
155
|
+
current_qq = data.get("qq", "")
|
|
156
|
+
if current_qq and current_qq.strip() == qq_number:
|
|
157
|
+
return name
|
|
158
|
+
return ""
|
|
159
|
+
|
|
160
|
+
def get_qq_player_history(self, qq_number: str) -> str:
|
|
161
|
+
"""根据QQ号获取历史绑定的玩家名(包括已解绑的)"""
|
|
162
|
+
for name, data in self._binding_data.items():
|
|
163
|
+
# 首先检查当前绑定的QQ号
|
|
164
|
+
if data.get("qq") == qq_number:
|
|
165
|
+
return name
|
|
166
|
+
# 检查原QQ号(用于被解绑或封禁的玩家历史查询)
|
|
167
|
+
if data.get("original_qq") == qq_number:
|
|
168
|
+
return name
|
|
169
|
+
return ""
|
|
170
|
+
|
|
171
|
+
def get_player_by_xuid(self, xuid: str) -> Dict[str, Any]:
|
|
172
|
+
"""根据XUID获取玩家绑定信息"""
|
|
173
|
+
return self._get_player_by_xuid(xuid)
|
|
174
|
+
|
|
175
|
+
def bind_player_qq(self, player_name: str, player_xuid: str, qq_number: str) -> bool:
|
|
176
|
+
"""绑定玩家QQ"""
|
|
177
|
+
# 验证参数
|
|
178
|
+
if not qq_number or not qq_number.strip():
|
|
179
|
+
self.logger.error(f"尝试绑定空QQ号给玩家 {player_name},操作被拒绝")
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
qq_clean = qq_number.strip()
|
|
183
|
+
if not qq_clean.isdigit() or len(qq_clean) < 5 or len(qq_clean) > 11:
|
|
184
|
+
self.logger.error(f"尝试绑定无效QQ号 {qq_clean} 给玩家 {player_name},QQ号必须是5-11位数字")
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
if not player_name or not player_name.strip():
|
|
188
|
+
self.logger.error(f"尝试绑定QQ {qq_clean} 给空玩家名,操作被拒绝")
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
# 检查是否已有该玩家的数据
|
|
192
|
+
if player_name in self._binding_data:
|
|
193
|
+
# 保留现有的游戏数据,更新绑定信息
|
|
194
|
+
player_data = self._binding_data[player_name]
|
|
195
|
+
old_qq = player_data.get("qq", "")
|
|
196
|
+
|
|
197
|
+
# 更新绑定信息
|
|
198
|
+
player_data["qq"] = qq_clean
|
|
199
|
+
player_data["xuid"] = player_xuid
|
|
200
|
+
|
|
201
|
+
if old_qq:
|
|
202
|
+
# 重新绑定
|
|
203
|
+
player_data["rebind_time"] = int(TimeUtils.get_timestamp())
|
|
204
|
+
player_data["previous_qq"] = old_qq
|
|
205
|
+
self.logger.info(f"玩家 {player_name} 重新绑定QQ: {old_qq} → {qq_clean}")
|
|
206
|
+
else:
|
|
207
|
+
# 首次绑定或解绑后重新绑定
|
|
208
|
+
if "unbind_time" in player_data:
|
|
209
|
+
player_data["rebind_time"] = int(TimeUtils.get_timestamp())
|
|
210
|
+
self.logger.info(f"玩家 {player_name} 解绑后重新绑定QQ: {qq_clean}")
|
|
211
|
+
else:
|
|
212
|
+
player_data["bind_time"] = int(TimeUtils.get_timestamp())
|
|
213
|
+
self.logger.info(f"玩家 {player_name} 首次绑定QQ: {qq_clean}")
|
|
214
|
+
else:
|
|
215
|
+
# 全新的玩家数据
|
|
216
|
+
self._binding_data[player_name] = {
|
|
217
|
+
"name": player_name,
|
|
218
|
+
"xuid": player_xuid,
|
|
219
|
+
"qq": qq_clean,
|
|
220
|
+
"bind_time": int(TimeUtils.get_timestamp()),
|
|
221
|
+
"total_playtime": 0,
|
|
222
|
+
"last_join_time": None,
|
|
223
|
+
"last_quit_time": None,
|
|
224
|
+
"session_count": 0
|
|
225
|
+
}
|
|
226
|
+
self.logger.info(f"玩家 {player_name} 已绑定QQ: {qq_clean}")
|
|
227
|
+
|
|
228
|
+
self.trigger_save(f"绑定QQ: {player_name} → {qq_clean}")
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def unbind_player_qq(self, player_name: str, admin_name: str = "system") -> bool:
|
|
232
|
+
"""解绑玩家QQ(保留游戏数据)"""
|
|
233
|
+
if player_name not in self._binding_data:
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
player_data = self._binding_data[player_name]
|
|
237
|
+
original_qq = player_data.get("qq", "")
|
|
238
|
+
|
|
239
|
+
if not original_qq or not original_qq.strip():
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
# 保留所有游戏数据,只清空QQ相关信息
|
|
243
|
+
player_data["qq"] = ""
|
|
244
|
+
player_data["unbind_time"] = int(TimeUtils.get_timestamp())
|
|
245
|
+
player_data["unbind_by"] = admin_name
|
|
246
|
+
player_data["original_qq"] = original_qq
|
|
247
|
+
|
|
248
|
+
self.trigger_save(f"解绑QQ: {player_name} (原QQ: {original_qq})")
|
|
249
|
+
self.logger.info(f"玩家 {player_name} 的QQ绑定已被 {admin_name} 解除 (原QQ: {original_qq}),游戏数据已保留")
|
|
250
|
+
return True
|
|
251
|
+
|
|
252
|
+
def update_player_name(self, old_name: str, new_name: str, xuid: str) -> bool:
|
|
253
|
+
"""更新玩家名称(处理改名情况)"""
|
|
254
|
+
if old_name in self._binding_data:
|
|
255
|
+
# 保存原有数据
|
|
256
|
+
player_data = self._binding_data[old_name].copy()
|
|
257
|
+
# 更新名称
|
|
258
|
+
player_data["name"] = new_name
|
|
259
|
+
player_data["last_name_update"] = int(TimeUtils.get_timestamp())
|
|
260
|
+
|
|
261
|
+
# 删除旧记录,添加新记录
|
|
262
|
+
del self._binding_data[old_name]
|
|
263
|
+
self._binding_data[new_name] = player_data
|
|
264
|
+
|
|
265
|
+
self.trigger_save(f"玩家改名: {old_name} → {new_name}")
|
|
266
|
+
self.logger.info(f"玩家改名: {old_name} → {new_name} (XUID: {xuid})")
|
|
267
|
+
return True
|
|
268
|
+
return False
|
|
269
|
+
|
|
270
|
+
# 游戏统计相关方法
|
|
271
|
+
def update_player_join(self, player_name: str, player_xuid: str = None):
|
|
272
|
+
"""更新玩家加入时间"""
|
|
273
|
+
if player_name not in self._binding_data:
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
# 检查玩家是否已绑定QQ
|
|
277
|
+
if not self._binding_data[player_name].get("qq", "").strip():
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
current_time = int(TimeUtils.get_timestamp())
|
|
281
|
+
self._binding_data[player_name]["last_join_time"] = current_time
|
|
282
|
+
self._binding_data[player_name]["session_count"] = self._binding_data[player_name].get("session_count", 0) + 1
|
|
283
|
+
|
|
284
|
+
# 更新XUID(如果提供了新的XUID)
|
|
285
|
+
if player_xuid and not self._binding_data[player_name].get("xuid"):
|
|
286
|
+
self._binding_data[player_name]["xuid"] = player_xuid
|
|
287
|
+
|
|
288
|
+
self.trigger_save(f"玩家加入: {player_name}")
|
|
289
|
+
|
|
290
|
+
def update_player_quit(self, player_name: str):
|
|
291
|
+
"""更新玩家离开时间和总在线时间"""
|
|
292
|
+
if player_name not in self._binding_data:
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
# 检查玩家是否已绑定QQ
|
|
296
|
+
if not self._binding_data[player_name].get("qq", "").strip():
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
current_time = int(TimeUtils.get_timestamp())
|
|
300
|
+
last_join = self._binding_data[player_name].get("last_join_time")
|
|
301
|
+
|
|
302
|
+
if last_join:
|
|
303
|
+
# 计算本次会话时间
|
|
304
|
+
session_time = current_time - last_join
|
|
305
|
+
if session_time > 0:
|
|
306
|
+
self._binding_data[player_name]["total_playtime"] = self._binding_data[player_name].get("total_playtime", 0) + session_time
|
|
307
|
+
|
|
308
|
+
self._binding_data[player_name]["last_quit_time"] = current_time
|
|
309
|
+
self.trigger_save(f"玩家离开: {player_name}")
|
|
310
|
+
|
|
311
|
+
def get_player_playtime_info(self, player_name: str, online_players: List[Any]) -> Dict[str, Any]:
|
|
312
|
+
"""获取玩家在线时间信息"""
|
|
313
|
+
if player_name not in self._binding_data:
|
|
314
|
+
return {}
|
|
315
|
+
|
|
316
|
+
data = self._binding_data[player_name]
|
|
317
|
+
|
|
318
|
+
# 检查玩家是否已绑定QQ
|
|
319
|
+
if not data.get("qq", "").strip():
|
|
320
|
+
return {}
|
|
321
|
+
|
|
322
|
+
current_time = int(TimeUtils.get_timestamp())
|
|
323
|
+
total_playtime = data.get("total_playtime", 0)
|
|
324
|
+
last_join = data.get("last_join_time")
|
|
325
|
+
|
|
326
|
+
# 如果玩家当前在线,加上当前会话时间
|
|
327
|
+
is_online = False
|
|
328
|
+
current_session_time = 0
|
|
329
|
+
if last_join:
|
|
330
|
+
# 检查玩家是否在线
|
|
331
|
+
for player in online_players:
|
|
332
|
+
if player.name == player_name:
|
|
333
|
+
is_online = True
|
|
334
|
+
current_session_time = current_time - last_join
|
|
335
|
+
break
|
|
336
|
+
|
|
337
|
+
total_with_current = total_playtime + (current_session_time if is_online else 0)
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
"total_playtime": total_with_current,
|
|
341
|
+
"session_count": data.get("session_count", 0),
|
|
342
|
+
"last_join_time": last_join,
|
|
343
|
+
"last_quit_time": data.get("last_quit_time"),
|
|
344
|
+
"is_online": is_online,
|
|
345
|
+
"current_session_time": current_session_time if is_online else 0,
|
|
346
|
+
"bind_time": data.get("bind_time")
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
# 封禁相关方法
|
|
350
|
+
def is_player_banned(self, player_name: str) -> bool:
|
|
351
|
+
"""检查玩家是否被封禁"""
|
|
352
|
+
if player_name not in self._binding_data:
|
|
353
|
+
return False
|
|
354
|
+
return self._binding_data[player_name].get("is_banned", False)
|
|
355
|
+
|
|
356
|
+
def ban_player(self, player_name: str, admin_name: str = "system", reason: str = "") -> bool:
|
|
357
|
+
"""封禁玩家"""
|
|
358
|
+
# 确保玩家数据存在
|
|
359
|
+
if player_name not in self._binding_data:
|
|
360
|
+
self._binding_data[player_name] = {
|
|
361
|
+
"name": player_name,
|
|
362
|
+
"xuid": "",
|
|
363
|
+
"qq": "",
|
|
364
|
+
"total_playtime": 0,
|
|
365
|
+
"last_join_time": None,
|
|
366
|
+
"last_quit_time": None,
|
|
367
|
+
"session_count": 0
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
# 设置封禁状态
|
|
371
|
+
player_data = self._binding_data[player_name]
|
|
372
|
+
player_data["is_banned"] = True
|
|
373
|
+
player_data["ban_time"] = int(TimeUtils.get_timestamp())
|
|
374
|
+
player_data["ban_by"] = admin_name
|
|
375
|
+
player_data["ban_reason"] = reason or "管理员封禁"
|
|
376
|
+
|
|
377
|
+
# 如果玩家已绑定QQ,解除绑定
|
|
378
|
+
if player_data.get("qq"):
|
|
379
|
+
original_qq = player_data["qq"]
|
|
380
|
+
player_data["qq"] = ""
|
|
381
|
+
player_data["unbind_time"] = int(TimeUtils.get_timestamp())
|
|
382
|
+
player_data["unbind_by"] = admin_name
|
|
383
|
+
player_data["unbind_reason"] = "封禁时自动解绑"
|
|
384
|
+
player_data["original_qq"] = original_qq
|
|
385
|
+
self.logger.info(f"玩家 {player_name} 被封禁时自动解除QQ绑定 (原QQ: {original_qq})")
|
|
386
|
+
|
|
387
|
+
self.trigger_save(f"封禁玩家: {player_name} (原因: {reason or '管理员封禁'})")
|
|
388
|
+
self.logger.info(f"玩家 {player_name} 已被 {admin_name} 封禁,原因:{reason or '管理员封禁'}")
|
|
389
|
+
return True
|
|
390
|
+
|
|
391
|
+
def unban_player(self, player_name: str, admin_name: str = "system") -> bool:
|
|
392
|
+
"""解封玩家"""
|
|
393
|
+
if player_name not in self._binding_data:
|
|
394
|
+
return False
|
|
395
|
+
|
|
396
|
+
player_data = self._binding_data[player_name]
|
|
397
|
+
if not player_data.get("is_banned", False):
|
|
398
|
+
return False
|
|
399
|
+
|
|
400
|
+
# 解除封禁
|
|
401
|
+
player_data["is_banned"] = False
|
|
402
|
+
player_data["unban_time"] = int(TimeUtils.get_timestamp())
|
|
403
|
+
player_data["unban_by"] = admin_name
|
|
404
|
+
|
|
405
|
+
self.trigger_save(f"解封玩家: {player_name}")
|
|
406
|
+
self.logger.info(f"玩家 {player_name} 已被 {admin_name} 解封")
|
|
407
|
+
return True
|
|
408
|
+
|
|
409
|
+
def get_banned_players(self) -> List[Dict[str, Any]]:
|
|
410
|
+
"""获取所有被封禁的玩家列表"""
|
|
411
|
+
banned_players = [
|
|
412
|
+
{
|
|
413
|
+
"name": player_name,
|
|
414
|
+
"ban_time": data.get("ban_time"),
|
|
415
|
+
"ban_by": data.get("ban_by", "unknown"),
|
|
416
|
+
"ban_reason": data.get("ban_reason", "无原因")
|
|
417
|
+
}
|
|
418
|
+
for player_name, data in self._binding_data.items()
|
|
419
|
+
if data.get("is_banned", False)
|
|
420
|
+
]
|
|
421
|
+
return banned_players
|
|
422
|
+
|
|
423
|
+
def get_player_binding_history(self, player_name: str) -> Dict[str, Any]:
|
|
424
|
+
"""获取玩家绑定历史信息"""
|
|
425
|
+
if player_name not in self._binding_data:
|
|
426
|
+
return {}
|
|
427
|
+
|
|
428
|
+
data = self._binding_data[player_name]
|
|
429
|
+
|
|
430
|
+
history = {
|
|
431
|
+
"current_qq": data.get("qq", ""),
|
|
432
|
+
"is_bound": bool(data.get("qq", "").strip()),
|
|
433
|
+
"bind_time": data.get("bind_time"),
|
|
434
|
+
"unbind_time": data.get("unbind_time"),
|
|
435
|
+
"rebind_time": data.get("rebind_time"),
|
|
436
|
+
"unbind_by": data.get("unbind_by"),
|
|
437
|
+
"original_qq": data.get("original_qq"),
|
|
438
|
+
"previous_qq": data.get("previous_qq"),
|
|
439
|
+
"total_playtime": data.get("total_playtime", 0),
|
|
440
|
+
"session_count": data.get("session_count", 0),
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# 计算绑定状态
|
|
444
|
+
if history["is_bound"]:
|
|
445
|
+
if history["rebind_time"]:
|
|
446
|
+
history["status"] = "重新绑定"
|
|
447
|
+
else:
|
|
448
|
+
history["status"] = "已绑定"
|
|
449
|
+
else:
|
|
450
|
+
if history["unbind_time"]:
|
|
451
|
+
history["status"] = "已解绑"
|
|
452
|
+
else:
|
|
453
|
+
history["status"] = "从未绑定"
|
|
454
|
+
|
|
455
|
+
return history
|
|
456
|
+
|
|
457
|
+
def get_complete_player_binding_status(self, player_name: str, player_xuid: str) -> Dict[str, Any]:
|
|
458
|
+
"""获取玩家完整的绑定状态信息"""
|
|
459
|
+
result = {
|
|
460
|
+
"is_bound": False,
|
|
461
|
+
"qq_number": "",
|
|
462
|
+
"binding_source": "",
|
|
463
|
+
"data_consistent": True,
|
|
464
|
+
"issues": []
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
# 检查基于玩家名的绑定
|
|
468
|
+
name_bound = False
|
|
469
|
+
name_qq = ""
|
|
470
|
+
if player_name in self._binding_data:
|
|
471
|
+
name_qq = self._binding_data[player_name].get("qq", "")
|
|
472
|
+
name_bound = bool(name_qq and name_qq.strip())
|
|
473
|
+
|
|
474
|
+
# 检查基于XUID的绑定
|
|
475
|
+
xuid_data = self._get_player_by_xuid(player_xuid)
|
|
476
|
+
xuid_bound = False
|
|
477
|
+
xuid_qq = ""
|
|
478
|
+
if xuid_data:
|
|
479
|
+
xuid_qq = xuid_data.get("qq", "")
|
|
480
|
+
xuid_bound = bool(xuid_qq and xuid_qq.strip())
|
|
481
|
+
|
|
482
|
+
# 分析绑定状态
|
|
483
|
+
if name_bound and xuid_bound:
|
|
484
|
+
if name_qq == xuid_qq:
|
|
485
|
+
result["is_bound"] = True
|
|
486
|
+
result["qq_number"] = name_qq
|
|
487
|
+
result["binding_source"] = "both"
|
|
488
|
+
else:
|
|
489
|
+
result["is_bound"] = False
|
|
490
|
+
result["data_consistent"] = False
|
|
491
|
+
result["issues"].append(f"QQ号不一致: 玩家名对应{name_qq}, XUID对应{xuid_qq}")
|
|
492
|
+
elif name_bound and not xuid_bound:
|
|
493
|
+
result["is_bound"] = name_bound
|
|
494
|
+
result["qq_number"] = name_qq
|
|
495
|
+
result["binding_source"] = "name"
|
|
496
|
+
result["issues"].append("仅玩家名有绑定记录,XUID无对应数据")
|
|
497
|
+
elif not name_bound and xuid_bound:
|
|
498
|
+
result["is_bound"] = xuid_bound
|
|
499
|
+
result["qq_number"] = xuid_qq
|
|
500
|
+
result["binding_source"] = "xuid"
|
|
501
|
+
result["issues"].append("仅XUID有绑定记录,当前玩家名无对应数据")
|
|
502
|
+
else:
|
|
503
|
+
result["is_bound"] = False
|
|
504
|
+
result["binding_source"] = "none"
|
|
505
|
+
|
|
506
|
+
return result
|
|
507
|
+
|
|
508
|
+
@property
|
|
509
|
+
def binding_data(self) -> Dict[str, Any]:
|
|
510
|
+
"""获取完整绑定数据的副本"""
|
|
511
|
+
return self._binding_data.copy()
|