feat(mmp): 领取人ID/模块版本检测,并修正状态条误报红
展示 receiverId 与 activityPoolId;检测 Hook 是否最新;按真实时间排序;有心跳与定时拉取时不再因久无新数据爆红。
This commit is contained in:
@@ -66,6 +66,17 @@
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>断线自动进历史页</h2>
|
||||
<p>设置可勾选「检测不到连接时:自动进历史页再返回」。</p>
|
||||
<ul>
|
||||
<li>session 丢失、历史 RPC 失败、或还没有请求模板时触发</li>
|
||||
<li>短暂打开 TNG 红包历史页约 2 秒后自动返回,用来重建连接</li>
|
||||
<li>约 45 秒最多一次,避免刷屏</li>
|
||||
<li>仍需要 TNG 进程在跑;完全杀掉后不会自己拉起</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>打开 TNG 闪退怎么办</h2>
|
||||
<p>装了 Xposed / 防护相关模块后,TNG 偶发一打开就闪退,属常见现象,可按顺序试:</p>
|
||||
|
||||
@@ -41,6 +41,14 @@
|
||||
padding: 7px 12px; border-radius: 6px; cursor: pointer; font-size: 13px;
|
||||
}
|
||||
.btn.primary { background: #ea580c; border-color: #ea580c; color: #fff; font-weight: 600; }
|
||||
.conn-bar {
|
||||
background: #1f2937; color: #e5e7eb; padding: 8px 16px; font-size: 12px;
|
||||
line-height: 1.45; flex-shrink: 0; border-bottom: 1px solid #111827;
|
||||
}
|
||||
.conn-bar.ok { background: #064e3b; color: #d1fae5; }
|
||||
.conn-bar.bad { background: #7f1d1d; color: #fee2e2; }
|
||||
.conn-bar.warn { background: #78350f; color: #fef3c7; }
|
||||
.uid { display: block; font-size: 11px; color: var(--muted); font-weight: 500; margin-top: 2px; }
|
||||
.toolbar {
|
||||
background: var(--panel); border-bottom: 1px solid var(--line);
|
||||
padding: 10px 16px; display: grid; gap: 8px; flex-shrink: 0;
|
||||
@@ -184,6 +192,7 @@
|
||||
<button class="btn" onclick="loadData()">刷新</button>
|
||||
<button class="btn primary" onclick="clearAll()">清空</button>
|
||||
</header>
|
||||
<div class="conn-bar warn" id="connBar">模块检测中…</div>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="row">
|
||||
@@ -223,6 +232,7 @@
|
||||
<input type="number" id="cfgPagePoll" min="500" max="30000" step="100" />
|
||||
</label>
|
||||
<label class="check"><input type="checkbox" id="cfgOpenHistory" /> 无模板时自动打开历史页</label>
|
||||
<label class="check"><input type="checkbox" id="cfgBounceHistory" /> 检测不到连接时:自动进历史页再返回(重建连接)</label>
|
||||
</div>
|
||||
<div class="settings-actions">
|
||||
<button type="button" class="chip" onclick="applyPreset('fast')">极速</button>
|
||||
@@ -267,7 +277,7 @@
|
||||
</div>
|
||||
<div class="section">领取排行</div>
|
||||
<table>
|
||||
<thead><tr><th class="rank">#</th><th>昵称</th><th>金额</th><th>时间</th></tr></thead>
|
||||
<thead><tr><th class="rank">#</th><th>昵称 / ID</th><th>金额</th><th>时间</th></tr></thead>
|
||||
<tbody id="board"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -277,26 +287,28 @@
|
||||
<script>
|
||||
let packets = [], activeId = null, pagePollTimer = null;
|
||||
let statusFilter = 'all', timeFilter = 'all', keyword = '';
|
||||
let settings = { historyCooldownSec: 8, detailCooldownSec: 8, dedupSec: 3, detailGapMs: 80, pagePollMs: 1000, openHistoryIfNoTemplate: true };
|
||||
let settings = { historyCooldownSec: 8, detailCooldownSec: 8, dedupSec: 3, detailGapMs: 80, pagePollMs: 1000, openHistoryIfNoTemplate: true, autoBounceHistoryOnDisconnect: true };
|
||||
|
||||
function toggleSettings(){ document.getElementById('settingsPanel').classList.toggle('open'); }
|
||||
function fillSettingsForm(){
|
||||
cfgHistory.value = settings.historyCooldownSec; cfgDetail.value = settings.detailCooldownSec;
|
||||
cfgDedup.value = settings.dedupSec; cfgGap.value = settings.detailGapMs;
|
||||
cfgPagePoll.value = settings.pagePollMs; cfgOpenHistory.checked = !!settings.openHistoryIfNoTemplate;
|
||||
cfgBounceHistory.checked = settings.autoBounceHistoryOnDisconnect !== false;
|
||||
cfgHint.textContent = '刷新: 历史' + settings.historyCooldownSec + 's / 详情' + settings.detailCooldownSec + 's';
|
||||
}
|
||||
function readSettingsForm(){
|
||||
return {
|
||||
historyCooldownSec: Number(cfgHistory.value), detailCooldownSec: Number(cfgDetail.value),
|
||||
dedupSec: Number(cfgDedup.value), detailGapMs: Number(cfgGap.value),
|
||||
pagePollMs: Number(cfgPagePoll.value), openHistoryIfNoTemplate: cfgOpenHistory.checked
|
||||
pagePollMs: Number(cfgPagePoll.value), openHistoryIfNoTemplate: cfgOpenHistory.checked,
|
||||
autoBounceHistoryOnDisconnect: cfgBounceHistory.checked
|
||||
};
|
||||
}
|
||||
function applyPreset(name){
|
||||
if (name==='fast') settings={historyCooldownSec:5,detailCooldownSec:5,dedupSec:2,detailGapMs:50,pagePollMs:800,openHistoryIfNoTemplate:true};
|
||||
else if (name==='slow') settings={historyCooldownSec:30,detailCooldownSec:45,dedupSec:15,detailGapMs:200,pagePollMs:3000,openHistoryIfNoTemplate:true};
|
||||
else settings={historyCooldownSec:8,detailCooldownSec:8,dedupSec:3,detailGapMs:80,pagePollMs:1000,openHistoryIfNoTemplate:true};
|
||||
if (name==='fast') settings={historyCooldownSec:5,detailCooldownSec:5,dedupSec:2,detailGapMs:50,pagePollMs:800,openHistoryIfNoTemplate:true,autoBounceHistoryOnDisconnect:true};
|
||||
else if (name==='slow') settings={historyCooldownSec:30,detailCooldownSec:45,dedupSec:15,detailGapMs:200,pagePollMs:3000,openHistoryIfNoTemplate:true,autoBounceHistoryOnDisconnect:true};
|
||||
else settings={historyCooldownSec:8,detailCooldownSec:8,dedupSec:3,detailGapMs:80,pagePollMs:1000,openHistoryIfNoTemplate:true,autoBounceHistoryOnDisconnect:true};
|
||||
fillSettingsForm();
|
||||
}
|
||||
async function loadSettings(){ settings = await (await fetch('/api/mmp/settings')).json(); fillSettingsForm(); restartPagePoll(); }
|
||||
@@ -348,7 +360,7 @@
|
||||
if(statusFilter==='done'&&!p.finished) return false;
|
||||
if(!inTimeRange(p)) return false;
|
||||
if(!keyword) return true;
|
||||
const blob=[p.packetId,p.sender,p.title,p.group,p.total,p.bestNick,p.worstNick,...(p.leaderboard||[]).map(r=>r.nickname)].join(' ').toLowerCase();
|
||||
const blob=[p.packetId,p.sender,p.title,p.group,p.total,p.bestNick,p.worstNick,...(p.leaderboard||[]).map(r=>[r.nickname,r.userId,r.poolId].join(' '))].join(' ').toLowerCase();
|
||||
return blob.indexOf(keyword)>=0;
|
||||
});
|
||||
}
|
||||
@@ -362,11 +374,28 @@
|
||||
}
|
||||
|
||||
async function loadData(){
|
||||
packets = await (await fetch('/api/mmp')).json();
|
||||
const [pktRes, stRes] = await Promise.all([
|
||||
fetch('/api/mmp'),
|
||||
fetch('/api/mmp/status')
|
||||
]);
|
||||
packets = await pktRes.json();
|
||||
updated.textContent = '更新 ' + new Date().toLocaleTimeString();
|
||||
try {
|
||||
const st = await stRes.json();
|
||||
renderConnBar(st);
|
||||
} catch (e) {
|
||||
renderConnBar({ text: '模块状态不可用', ok: false });
|
||||
}
|
||||
renderList();
|
||||
}
|
||||
|
||||
function renderConnBar(st){
|
||||
const el = document.getElementById('connBar');
|
||||
if (!el) return;
|
||||
el.textContent = (st && st.text) ? st.text : '模块状态未知';
|
||||
el.className = 'conn-bar ' + (st && st.ok ? 'ok' : (st && st.liveAt ? 'bad' : 'warn'));
|
||||
}
|
||||
|
||||
function renderList(){
|
||||
const view=filteredPackets();
|
||||
pktCount.textContent = view.length + '/' + packets.length + ' 个';
|
||||
@@ -442,9 +471,12 @@
|
||||
let nick=esc(row.nickname);
|
||||
if(isBest) nick += '<span class="tag-inline best">最高</span>';
|
||||
if(isWorst) nick += '<span class="tag-inline worst">最低</span>';
|
||||
let nickHtml=`<div>${nick}</div>`;
|
||||
if(row.userId) nickHtml += `<span class="uid">用户ID ${esc(row.userId)}</span>`;
|
||||
if(row.poolId) nickHtml += `<span class="uid">领取ID ${esc(row.poolId)}</span>`;
|
||||
tr.innerHTML=`
|
||||
<td class="rank">${row.rank}</td>
|
||||
<td class="nick">${nick}</td>
|
||||
<td class="nick">${nickHtml}</td>
|
||||
<td class="amt">${esc(row.amountText || money(row.amount))}</td>
|
||||
<td>${esc(row.claimTime || '-')}</td>`;
|
||||
board.appendChild(tr);
|
||||
|
||||
@@ -16,10 +16,23 @@ PORT = 8765
|
||||
MAX_MESSAGES = 500
|
||||
SETTINGS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mmp_settings.json")
|
||||
PACKETS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mmp_packets.json")
|
||||
HOOK_STATUS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mmp_hook_status.json")
|
||||
|
||||
# 与 xposed-module / App MmpModuleInfo 同步
|
||||
EXPECTED_MODULE_CODE = 3
|
||||
EXPECTED_MODULE_NAME = "1.2.0"
|
||||
|
||||
_messages = deque(maxlen=MAX_MESSAGES)
|
||||
_mmp_messages = deque(maxlen=MAX_MESSAGES)
|
||||
_mmp_synced = {} # packetId -> packet dict(手机全量同步,落盘)
|
||||
_mmp_hook_status = {
|
||||
"liveCode": 0,
|
||||
"liveName": "",
|
||||
"liveAt": 0, # epoch ms
|
||||
"installedCode": 0,
|
||||
"installedName": "",
|
||||
"source": "",
|
||||
}
|
||||
_lock = threading.Lock()
|
||||
_last_dedup = {"key": None, "ts": 0.0}
|
||||
_mmp_last_dedup = {"key": None, "ts": 0.0}
|
||||
@@ -31,6 +44,7 @@ _DEFAULT_MMP_SETTINGS = {
|
||||
"detailGapMs": 80,
|
||||
"pagePollMs": 1000,
|
||||
"openHistoryIfNoTemplate": True,
|
||||
"autoBounceHistoryOnDisconnect": True,
|
||||
}
|
||||
_mmp_settings = dict(_DEFAULT_MMP_SETTINGS)
|
||||
|
||||
@@ -69,6 +83,8 @@ def _normalize_mmp_settings(raw):
|
||||
out["pagePollMs"] = max(500, min(30000, int(raw.get("pagePollMs", out["pagePollMs"]))))
|
||||
out["openHistoryIfNoTemplate"] = bool(raw.get(
|
||||
"openHistoryIfNoTemplate", out["openHistoryIfNoTemplate"]))
|
||||
out["autoBounceHistoryOnDisconnect"] = bool(raw.get(
|
||||
"autoBounceHistoryOnDisconnect", out["autoBounceHistoryOnDisconnect"]))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return out
|
||||
@@ -114,6 +130,7 @@ def _add_message(payload):
|
||||
])
|
||||
now = time.time()
|
||||
is_mmp = _is_mmp_message(item)
|
||||
content_for_mod = ""
|
||||
with _lock:
|
||||
if is_mmp:
|
||||
if (_mmp_last_dedup["key"] == dedup_key
|
||||
@@ -125,6 +142,7 @@ def _add_message(payload):
|
||||
_mmp_messages.appendleft(item)
|
||||
item["id"] = len(_mmp_messages)
|
||||
channel = "MMP"
|
||||
content_for_mod = item.get("content") or ""
|
||||
else:
|
||||
if (_last_dedup["key"] == dedup_key
|
||||
and now - float(_last_dedup["ts"]) < 3.0):
|
||||
@@ -135,6 +153,8 @@ def _add_message(payload):
|
||||
_messages.appendleft(item)
|
||||
item["id"] = len(_messages)
|
||||
channel = "MSG"
|
||||
if content_for_mod:
|
||||
_note_module_from_content(content_for_mod)
|
||||
print("[{0}] [{1}] [{2}] [{3}] {4} | {5}".format(
|
||||
_now_iso(),
|
||||
channel,
|
||||
@@ -287,17 +307,40 @@ def _parse_mmp_content(content):
|
||||
left, right = line.split("->", 1)
|
||||
nick = left.strip()
|
||||
right = right.strip()
|
||||
user_id = ""
|
||||
pool_id = ""
|
||||
if " #rid=" in right:
|
||||
before, _, rest = right.partition(" #rid=")
|
||||
right = before.strip()
|
||||
rid_part = rest.strip()
|
||||
if " #pool=" in rid_part:
|
||||
user_id, _, pool_rest = rid_part.partition(" #pool=")
|
||||
user_id = user_id.strip()
|
||||
pool_id = pool_rest.strip()
|
||||
elif " " in rid_part:
|
||||
user_id = rid_part.split(" ", 1)[0].strip()
|
||||
else:
|
||||
user_id = rid_part
|
||||
if "#pool=" in user_id:
|
||||
user_id, _, pool_id = user_id.partition("#pool=")
|
||||
user_id = user_id.strip()
|
||||
pool_id = pool_id.strip()
|
||||
if " #pool=" in right and not pool_id:
|
||||
before, _, pool_rest = right.partition(" #pool=")
|
||||
right = before.strip()
|
||||
pool_id = pool_rest.strip()
|
||||
claim_time = ""
|
||||
if right.endswith(")") and "(" in right and not right.startswith("{"):
|
||||
amount_raw = right
|
||||
if "(" in right and ")" in right and not right.startswith("{"):
|
||||
amt, _, rest = right.partition("(")
|
||||
amount_raw = amt.strip()
|
||||
claim_time = rest.rstrip(")").strip()
|
||||
else:
|
||||
amount_raw = right
|
||||
claim_time = rest.split(")", 1)[0].strip()
|
||||
amount_num = _normalize_money(amount_raw)
|
||||
if nick:
|
||||
if nick or user_id:
|
||||
claims.append({
|
||||
"nickname": nick,
|
||||
"nickname": nick or "?",
|
||||
"userId": user_id,
|
||||
"poolId": pool_id,
|
||||
"amount": ("{0:.2f}".format(amount_num) if amount_num is not None else "0.00"),
|
||||
"amountValue": amount_num if amount_num is not None else 0.0,
|
||||
"claimTime": claim_time,
|
||||
@@ -308,40 +351,61 @@ def _parse_mmp_content(content):
|
||||
def _claims_fingerprint(claims):
|
||||
rows = []
|
||||
for c in claims:
|
||||
rows.append("{0}={1}".format(c.get("nickname") or "?", c.get("amount") or "0"))
|
||||
key = c.get("userId") or c.get("nickname") or "?"
|
||||
rows.append("{0}={1}".format(key, c.get("amount") or "0"))
|
||||
rows.sort()
|
||||
return "|".join(rows)
|
||||
|
||||
|
||||
def _aggregate_claims(claims):
|
||||
"""同一快照内同昵称只保留一笔(取较大金额),红包每人只领一次,禁止累加导致翻倍。"""
|
||||
"""同一快照内按 userId(无则昵称)去重,取较大金额,禁止同人累加翻倍。"""
|
||||
buckets = {}
|
||||
order = []
|
||||
for c in claims:
|
||||
nick = c.get("nickname") or "?"
|
||||
uid = (c.get("userId") or "").strip()
|
||||
key = ("id:" + uid) if uid else ("n:" + nick)
|
||||
try:
|
||||
amt = float(c.get("amountValue") if c.get("amountValue") is not None
|
||||
else _normalize_money(c.get("amount")) or 0.0)
|
||||
except (TypeError, ValueError):
|
||||
amt = 0.0
|
||||
if nick not in buckets:
|
||||
buckets[nick] = {"nickname": nick, "amount": amt, "count": 1,
|
||||
"claimTime": c.get("claimTime") or ""}
|
||||
order.append(nick)
|
||||
if key not in buckets:
|
||||
buckets[key] = {
|
||||
"nickname": nick,
|
||||
"userId": uid,
|
||||
"poolId": (c.get("poolId") or "").strip(),
|
||||
"amount": amt,
|
||||
"count": 1,
|
||||
"claimTime": c.get("claimTime") or "",
|
||||
}
|
||||
order.append(key)
|
||||
continue
|
||||
b = buckets[nick]
|
||||
b = buckets[key]
|
||||
b["count"] += 1
|
||||
if uid and not b.get("userId"):
|
||||
b["userId"] = uid
|
||||
if c.get("poolId") and not b.get("poolId"):
|
||||
b["poolId"] = c.get("poolId") or ""
|
||||
if amt >= b["amount"]:
|
||||
b["amount"] = amt
|
||||
if nick and nick != "?":
|
||||
b["nickname"] = nick
|
||||
if c.get("claimTime"):
|
||||
b["claimTime"] = c.get("claimTime") or b["claimTime"]
|
||||
if uid:
|
||||
b["userId"] = uid
|
||||
if c.get("poolId"):
|
||||
b["poolId"] = c.get("poolId") or b.get("poolId") or ""
|
||||
elif c.get("claimTime") and not b["claimTime"]:
|
||||
b["claimTime"] = c.get("claimTime") or ""
|
||||
result = []
|
||||
for nick in order:
|
||||
b = buckets[nick]
|
||||
for key in order:
|
||||
b = buckets[key]
|
||||
result.append({
|
||||
"nickname": nick,
|
||||
"nickname": b["nickname"],
|
||||
"userId": b.get("userId") or "",
|
||||
"poolId": b.get("poolId") or "",
|
||||
"amount": round(b["amount"], 4),
|
||||
"amountText": "{0:.2f}".format(b["amount"]),
|
||||
"claimCount": b["count"],
|
||||
@@ -353,6 +417,159 @@ def _aggregate_claims(claims):
|
||||
return result
|
||||
|
||||
|
||||
def _parse_mod_token(mod):
|
||||
"""mod=1.2.0/3 → (name, code)"""
|
||||
text = (mod or "").strip()
|
||||
if not text:
|
||||
return "", 0
|
||||
name = text
|
||||
code = 0
|
||||
if "/" in text:
|
||||
name, _, code_part = text.rpartition("/")
|
||||
name = name.strip()
|
||||
try:
|
||||
code = int(code_part.strip())
|
||||
except (TypeError, ValueError):
|
||||
code = 0
|
||||
return name, code
|
||||
|
||||
|
||||
def _note_module_from_content(content):
|
||||
if not content or "[MMP统计]" not in content:
|
||||
return
|
||||
head = content.strip().split("\n", 1)[0]
|
||||
if "mod=" not in head:
|
||||
return
|
||||
raw = head.split("mod=", 1)[1]
|
||||
for stop in (" | ", " src=", " "):
|
||||
if stop in raw:
|
||||
raw = raw.split(stop, 1)[0]
|
||||
break
|
||||
name, code = _parse_mod_token(raw)
|
||||
_update_hook_status({
|
||||
"liveCode": code,
|
||||
"liveName": name,
|
||||
"liveAt": int(time.time() * 1000),
|
||||
"source": "message",
|
||||
})
|
||||
|
||||
|
||||
def _load_hook_status():
|
||||
global _mmp_hook_status
|
||||
try:
|
||||
if not os.path.isfile(HOOK_STATUS_PATH):
|
||||
return
|
||||
with open(HOOK_STATUS_PATH, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
if isinstance(data, dict):
|
||||
with _lock:
|
||||
_mmp_hook_status.update({
|
||||
"liveCode": int(data.get("liveCode") or 0),
|
||||
"liveName": str(data.get("liveName") or ""),
|
||||
"liveAt": int(data.get("liveAt") or 0),
|
||||
"installedCode": int(data.get("installedCode") or 0),
|
||||
"installedName": str(data.get("installedName") or ""),
|
||||
"source": str(data.get("source") or ""),
|
||||
})
|
||||
except Exception as e:
|
||||
print("load mmp hook status failed:", e)
|
||||
|
||||
|
||||
def _save_hook_status():
|
||||
try:
|
||||
with _lock:
|
||||
data = dict(_mmp_hook_status)
|
||||
with open(HOOK_STATUS_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
print("save mmp hook status failed:", e)
|
||||
|
||||
|
||||
def _update_hook_status(patch):
|
||||
if not isinstance(patch, dict):
|
||||
return
|
||||
with _lock:
|
||||
try:
|
||||
incoming_live_at = int(patch.get("liveAt") or 0)
|
||||
except (TypeError, ValueError):
|
||||
incoming_live_at = 0
|
||||
stale_live = (incoming_live_at > 0
|
||||
and int(_mmp_hook_status.get("liveAt") or 0) > 0
|
||||
and incoming_live_at < int(_mmp_hook_status.get("liveAt") or 0))
|
||||
for k in ("liveCode", "liveName", "liveAt", "installedCode", "installedName", "source"):
|
||||
if k not in patch:
|
||||
continue
|
||||
if stale_live and k in ("liveCode", "liveName", "liveAt", "source"):
|
||||
continue
|
||||
# 无心跳时不要用 0 覆盖已有运行版本
|
||||
if incoming_live_at <= 0 and k in ("liveCode", "liveName", "liveAt"):
|
||||
continue
|
||||
val = patch[k]
|
||||
if k in ("liveCode", "liveAt", "installedCode"):
|
||||
try:
|
||||
val = int(val or 0)
|
||||
except (TypeError, ValueError):
|
||||
val = 0
|
||||
else:
|
||||
val = str(val or "")
|
||||
if k in ("installedCode",) and val <= 0:
|
||||
continue
|
||||
if k in ("installedName",) and not val:
|
||||
continue
|
||||
_mmp_hook_status[k] = val
|
||||
_save_hook_status()
|
||||
|
||||
|
||||
def _describe_hook_status():
|
||||
with _lock:
|
||||
st = dict(_mmp_hook_status)
|
||||
installed = int(st.get("installedCode") or 0)
|
||||
installed_name = st.get("installedName") or ""
|
||||
live_code = int(st.get("liveCode") or 0)
|
||||
live_name = st.get("liveName") or ""
|
||||
live_at = int(st.get("liveAt") or 0)
|
||||
now_ms = int(time.time() * 1000)
|
||||
|
||||
if installed > 0:
|
||||
apk = "模块APK:" + (installed_name or str(installed))
|
||||
if installed < EXPECTED_MODULE_CODE:
|
||||
apk += "(旧,期望{0})".format(EXPECTED_MODULE_NAME)
|
||||
else:
|
||||
apk = "模块APK:未知(等手机同步)"
|
||||
|
||||
age = (now_ms - live_at) if live_at > 0 else -1
|
||||
if age < 0:
|
||||
live = "运行中:未检测到(请强停再开 TNG)"
|
||||
ok = False
|
||||
elif age > 3 * 60 * 1000:
|
||||
live = "运行中:心跳偏旧({0}分钟前 {1})".format(
|
||||
age // 60000, live_name or live_code or "?")
|
||||
ok = False
|
||||
elif live_code >= EXPECTED_MODULE_CODE:
|
||||
live = "运行中:最新({0})".format(live_name or live_code)
|
||||
ok = True
|
||||
elif live_code > 0:
|
||||
live = "运行中:旧版({0},期望{1})".format(
|
||||
live_name or live_code, EXPECTED_MODULE_NAME)
|
||||
ok = False
|
||||
else:
|
||||
live = "运行中:未知"
|
||||
ok = False
|
||||
|
||||
return {
|
||||
"ok": ok,
|
||||
"text": apk + " · " + live,
|
||||
"expectedCode": EXPECTED_MODULE_CODE,
|
||||
"expectedName": EXPECTED_MODULE_NAME,
|
||||
"liveCode": live_code,
|
||||
"liveName": live_name,
|
||||
"liveAt": live_at,
|
||||
"installedCode": installed,
|
||||
"installedName": installed_name,
|
||||
"ageMs": age,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def _packet_score(it):
|
||||
board = it.get("leaderboard") or it.get("claims") or []
|
||||
@@ -387,6 +604,8 @@ def _normalize_synced_packet(raw):
|
||||
nick = (row.get("nickname") or "?").strip() or "?"
|
||||
norm_board.append({
|
||||
"nickname": nick,
|
||||
"userId": (row.get("userId") or "").strip(),
|
||||
"poolId": (row.get("poolId") or "").strip(),
|
||||
"amount": round(amt, 4),
|
||||
"amountText": row.get("amountText") or "{0:.2f}".format(amt),
|
||||
"claimTime": row.get("claimTime") or "",
|
||||
@@ -471,6 +690,16 @@ def _sync_mmp_packets(payload):
|
||||
items = []
|
||||
if isinstance(payload, dict):
|
||||
items = payload.get("packets") or []
|
||||
hook = payload.get("hookStatus")
|
||||
if isinstance(hook, dict):
|
||||
_update_hook_status({
|
||||
"liveCode": hook.get("liveCode") or hook.get("moduleVersionCode") or 0,
|
||||
"liveName": hook.get("liveName") or hook.get("moduleVersionName") or "",
|
||||
"liveAt": hook.get("liveAt") or hook.get("updatedAt") or int(time.time() * 1000),
|
||||
"installedCode": hook.get("installedCode") or 0,
|
||||
"installedName": hook.get("installedName") or "",
|
||||
"source": "phone-sync",
|
||||
})
|
||||
elif isinstance(payload, list):
|
||||
items = payload
|
||||
if not isinstance(items, list):
|
||||
@@ -806,6 +1035,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if path == "/api/mmp":
|
||||
_json_response(self, 200, _mmp_packets())
|
||||
return
|
||||
if path == "/api/mmp/status":
|
||||
_json_response(self, 200, _describe_hook_status())
|
||||
return
|
||||
if path == "/api/mmp/settings":
|
||||
_json_response(self, 200, _get_mmp_settings())
|
||||
return
|
||||
@@ -889,6 +1121,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
|
||||
_load_mmp_synced()
|
||||
_load_hook_status()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
Reference in New Issue
Block a user