Greasy Fork is available in English.
深度修复隐藏逻辑,确保点击隐藏后立即生效并持久化
// ==UserScript==
// @name NetChk_Triangle_IronLock v37 beta
// @version 37.5.31.b3
// @namespace http://greasyfork.icu/zh-CN/users/1593463-nander
// @license CC BY-NC-SA 4.0
// @description 深度修复隐藏逻辑,确保点击隐藏后立即生效并持久化
// @author Gemini
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_openInTab
// @grant GM_registerMenuCommand
// @connect *
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
const COLORS = {
bg: '#1a1a1a',
border: '#333333',
textMain: '#ffffff',
text80: '#cccccc',
text60: '#999999',
text40: '#666666',
vscBlue: '#005a9e',
nckCN: '#4ade80',
nckGL: '#60a5fa',
nckERR: '#f87171',
nckWarn: '#fbbf24'
};
const STYLES = `
#nck-main {
position: fixed !important; z-index: 2147483647 !important;
background: ${COLORS.bg} !important; border: 1px solid ${COLORS.border} !important;
border-radius: 2px !important; height: 24px !important;
display: flex !important; align-items: stretch !important;
box-sizing: border-box !important; cursor: default !important;
user-select: none !important; font-family: "Cascadia Code", Consolas, monospace !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.5) !important;
transition: opacity 0.3s, background 0.3s;
}
#nck-panel-container {
position: absolute !important; display: none !important;
left: 50% !important; transform: translateX(-50%) !important; width: 210px !important;
}
#nck-panel-container::before { content: ''; position: absolute; left: 0; right: 0; height: 10px; }
.dir-down { top: 24px !important; padding-top: 5px !important; }
.dir-down::before { top: -10px; }
.dir-up { bottom: 24px !important; padding-bottom: 5px !important; }
.dir-up::before { bottom: -10px; }
#nck-main:hover #nck-panel-container, #nck-main.keep-open #nck-panel-container { display: block !important; }
#nck-panel {
background: ${COLORS.bg} !important; border: 1px solid ${COLORS.border} !important;
padding: 6px !important; box-shadow: 0 4px 15px rgba(0,0,0,0.7) !important;
}
.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; border-bottom: 1px solid #333; padding-bottom: 3px; }
.panel-ver { font-size: 8px; color: ${COLORS.text40}; font-weight: bold; }
.ip-link-btn { cursor: pointer; color: ${COLORS.text60}; font-size: 8px; padding: 0 4px; border: 1px solid #444; background: #252526; }
.ip-row { background: #222; padding: 4px 6px; margin-bottom: 3px; border: 1px solid #2d2d2d; }
.ip-row-info { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2px; }
.ip-label { font-size: 8px; color: ${COLORS.text40}; font-weight: bold; }
.ip-label b { color: ${COLORS.text60}; margin-left: 5px; font-weight: normal; }
.ip-ms { font-size: 9px; font-weight: 900; opacity: 0.9;}
.ip-val {
color: ${COLORS.text80}; font-size: 10px; font-family: "Cascadia Code", monospace;
word-break: break-all; line-height: 1.2; display: block;
border-top: 1px solid #2d2d2d; padding-top: 3px; margin-top: 2px;
}
.ms-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; margin: 5px 0; }
.ms-node { font-size: 9px; text-align: center; color: ${COLORS.text40}; background: #252526; padding: 3px 0; border: 1px solid #333; }
.ms-node.active { color: ${COLORS.text80}; border-color: #444; }
.ms-node b { display: block; font-size: 7px; opacity: 0.6; margin-bottom: 1px; }
.btn-group { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px; margin-top: 5px; }
.t-btn { background: #2a2a2a; color: ${COLORS.text60}; font-size: 8px; padding: 4px 0; text-align: center; cursor: pointer; border: 1px solid #333; transition: 0.1s; }
.t-btn:hover { background: #333; color: #fff; }
.t-btn.active { background: ${COLORS.vscBlue} !important; color: #fff !important; border-color: #007acc !important; }
#nck-trigger { display: flex; align-items: center; padding: 0 8px; flex-grow: 1; cursor: pointer; gap: 6px; }
.info-code { color: #fff; font-size: 10px; font-weight: bold; }
.info-ms { font-size: 10px; font-weight: 900;min-width: 30px; }
.ms-cn { color: ${COLORS.nckCN}; }
.ms-gl { color: ${COLORS.nckGL}; }
.nck-led { width: 4px; height: 4px; border-radius: 1px; background: #333; transition: 0.3s; }
.led-checking { background: ${COLORS.nckWarn} !important; box-shadow: 0 0 4px ${COLORS.nckWarn}; }
.led-ok-cn { background: ${COLORS.nckCN} !important; box-shadow: 0 0 3px ${COLORS.nckCN}; }
.led-ok-gl { background: ${COLORS.nckGL} !important; box-shadow: 0 0 3px ${COLORS.nckGL}; }
.led-err { background: ${COLORS.nckERR} !important; }
#nck-bay { width: 24px; border-left: 1px solid ${COLORS.border}; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
#nck-bay:hover { background: #2a2a2a; }
.bay-play { width: 0; height: 0; border-top: 4px solid transparent; border-bottom: 4px solid transparent; border-left: 6px solid #555; transition: 0.2s; }
.ctrl-active .bay-play { border-left-color: ${COLORS.nckCN} !important; }
@keyframes success-flash { 0% { background: #1a1a1a; } 50% { background: #1a4a2a; } 100% { background: #1a1a1a; } }
.state-success { animation: success-flash 0.4s ease; }
.state-success .bay-play { border-left-color: ${COLORS.nckCN} !important; transform: scale(1.2); }
@keyframes error-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }
.state-error { animation: error-shake 0.2s 2; border-color: ${COLORS.nckERR} !important; }
.state-error .bay-play { border-left-color: ${COLORS.nckERR} !important; }
.hidden { display: none !important; }
`;
let state = {
isProcessing: false,
nodes: {
cn: { ip: '...', ms: '--', state: 'init' },
gl: { ip: '...', ms: '--', state: 'init' },
code: 'VSC'
},
targets: [
{ id: 'BD', url: 'https://www.baidu.com/favicon.ico' },
{ id: 'GH', url: 'https://github.com/favicon.ico' },
{ id: 'CF', url: 'https://1.1.1.1/cdn-cgi/trace' }
],
conf: {
visible: GM_getValue('nck_v378_vis', true),
matrix: GM_getValue('nck_v378_mtx', true),
longShow: GM_getValue('nck_v378_long', false),
locked: GM_getValue('nck_v378_lock', false),
bindCtrl: GM_getValue('nck_v378_ctrl', true),
bayOn: GM_getValue('nck_v378_bay', true),
pingShow: GM_getValue('nck_v378_ping', true),
lang: GM_getValue('nck_v378_lang', 'zh')
},
langLib: {
zh: { mtx:'矩阵', bay:'弹仓', keep:'驻留', lock:'锁定', ctrl:'劫持', lang:'中文', hide:'隐藏', ping:'数值' },
en: { mtx:'MTX', bay:'BAY', keep:'KEEP', lock:'LOCK', ctrl:'CTRL', lang:'EN', hide:'HIDE', ping:'PING' }
}
};
function syncUI() {
const box = document.getElementById('nck-main');
if (!box) return;
// 深度修复:强制设置 display 属性,确保可见性受控
if (state.conf.visible) {
box.style.setProperty('display', 'flex', 'important');
} else {
box.style.setProperty('display', 'none', 'important');
}
box.classList.toggle('keep-open', state.conf.longShow);
const bay = document.getElementById('nck-bay');
if (bay) {
bay.classList.toggle('hidden', !state.conf.bayOn);
bay.classList.toggle('ctrl-active', state.conf.bindCtrl);
}
// 刷新内容
const msCN = state.nodes.cn.ms ;
const msGL = state.nodes.gl.ms ;
// 在 syncUI 函数内部找到 infoBox 渲染逻辑并替换:
const infoBox = document.getElementById('nck-info-box');
if (infoBox) {
// 确保即使在加载中,也有占位符或正确的数值显示
const displayCN = state.conf.pingShow ? (state.nodes.cn.ms || '--') : '';
const displayGL = state.conf.pingShow ? (state.nodes.gl.ms || '--') : '';
infoBox.innerHTML = `
<span class="info-code">${state.nodes.code || 'VSC'}</span>
<span class="info-ms ms-cn">${displayCN}</span>
<span class="info-ms ms-gl">${displayGL}</span>`;
}
// LED
const ledCN = document.getElementById('led-cn');
const ledGL = document.getElementById('led-gl');
if (ledCN) ledCN.className = 'nck-led' + (state.nodes.cn.state === 'ok' ? ' led-ok-cn' : (state.nodes.cn.state === 'checking' ? ' led-checking' : ''));
if (ledGL) ledGL.className = 'nck-led' + (state.nodes.gl.state === 'ok' ? ' led-ok-gl' : (state.nodes.gl.state === 'checking' ? ' led-checking' : ''));
// 面板文本
const valCN = document.getElementById('ip-cn-val');
const valGL = document.getElementById('ip-gl-val');
if (valCN) valCN.innerText = state.nodes.cn.ip;
if (valGL) valGL.innerText = state.nodes.gl.ip;
// 2. 【新增/修复】刷新面板内部的延迟数值 (Label)
const labelCN = document.getElementById('ip-cn-ms-label');
const labelGL = document.getElementById('ip-gl-ms-label');
if (labelCN) labelCN.innerText = msCN; // 更新面板 CN 行的延迟
if (labelGL) labelGL.innerText = msGL; // 更新面板 GLB 行的延迟
// 按钮
const L = state.langLib[state.conf.lang];
const btnConfigs = [
{ id: 't-matrix', key: 'matrix', text: L.mtx },
{ id: 't-bayOn', key: 'bayOn', text: L.bay },
{ id: 't-longShow', key: 'longShow', text: L.keep },
{ id: 't-locked', key: 'locked', text: L.lock },
{ id: 't-bindCtrl', key: 'bindCtrl', text: L.ctrl },
{ id: 't-pingShow', key: 'pingShow', text: L.ping },
{ id: 't-lang', key: null, text: L.lang },
{ id: 't-hide', key: null, text: L.hide }
];
btnConfigs.forEach(cfg => {
const el = document.getElementById(cfg.id);
if (el) {
el.innerText = cfg.text;
if (cfg.key) el.classList.toggle('active', state.conf[cfg.key]);
}
});
const matrixArea = document.getElementById('matrix-area');
if (matrixArea) matrixArea.classList.toggle('hidden', !state.conf.matrix);
renderMatrix();
updatePanelPos();
}
function centerBox() {
const box = document.getElementById('nck-main');
if (box) {
const x = (window.innerWidth - box.offsetWidth) / 2;
const y = (window.innerHeight - box.offsetHeight) / 2;
box.style.left = x + 'px';
box.style.top = y + 'px';
GM_setValue('nck_v378_pos', { x, y });
updatePanelPos();
}
}
function renderMatrix() {
const grid = document.getElementById('grid-1');
if (grid) grid.innerHTML = state.targets.map(t => `<div class="ms-node ${t.ms ? 'active' : ''}"><b>${t.id}</b>${t.ms || '--'}</div>`).join('');
}
function updatePanelPos() {
const box = document.getElementById('nck-main');
const container = document.getElementById('nck-panel-container');
if (box && container) {
const rect = box.getBoundingClientRect();
container.className = rect.top > window.innerHeight / 2 ? 'dir-up' : 'dir-down';
}
}
function checkNet() {
const start = Date.now();
state.nodes.cn.state = 'checking'; state.nodes.gl.state = 'checking';
syncUI();
GM_xmlhttpRequest({
method: "GET",
url: "https://myip.ipip.net",
timeout: 2500,
onload: (r) => {
// IPIP 返回的是纯文本,形如 "IP: 1.2.3.4 来自于: 中国..."
// 我们用正则直接提取出 IP 地址部分
const match = r.responseText.match(/((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}/);
if (match) {
state.nodes.cn.ip = match[0];
state.nodes.cn.ms = (Date.now() - start) + 'ms';
state.nodes.cn.state = 'ok';
} else {
state.nodes.cn.state = 'err';
}
syncUI();
},
onerror: () => {
state.nodes.cn.state = 'err';
syncUI();
}
});
GM_xmlhttpRequest({
method: "GET", url: "https://www.cloudflare.com/cdn-cgi/trace", timeout: 2500,
onload: (r) => {
const ip = r.responseText.match(/ip=(.+)/);
const loc = r.responseText.match(/loc=(.+)/);
if (ip) state.nodes.gl.ip = ip[1];
if (loc) state.nodes.code = loc[1];
state.nodes.gl.ms = (Date.now() - start) + 'ms';
state.nodes.gl.state = 'ok';
syncUI();
},
onerror: () => { state.nodes.gl.state = 'err'; syncUI(); }
});
state.targets.forEach(t => {
const s = Date.now();
GM_xmlhttpRequest({
method: "GET", url: t.url + "?t=" + s, timeout: 2000,
onload: () => { t.ms = (Date.now() - s) + 'ms'; renderMatrix(); }
});
});
}
async function handleSend() {
if (state.isProcessing) return;
state.isProcessing = true;
const main = document.getElementById('nck-main');
state.nodes.cn.state = 'checking'; state.nodes.gl.state = 'checking'; syncUI();
const isOk = await new Promise(r => {
GM_xmlhttpRequest({
method: "GET", url: "https://www.google.com/generate_204", timeout: 1500,
onload: () => r(true), onerror: () => r(false), ontimeout: () => r(false)
});
});
if (isOk) {
main.classList.add('state-success');
const selectors = ['button[aria-label*="发送"]', 'button[aria-label*="Send"]', '[data-testid="send-button"]', 'button.send-icon', 'button[type="submit"]', '#send-btn'];
for (let s of selectors) {
const btn = document.querySelector(s);
if (btn && !btn.disabled) { btn.click(); break; }
}
setTimeout(() => main.classList.remove('state-success'), 500);
} else {
main.classList.add('state-error');
setTimeout(() => main.classList.remove('state-error'), 800);
}
state.isProcessing = false;
checkNet();
}
function inject() {
if (document.getElementById('nck-main')) return;
GM_addStyle(STYLES);
const box = document.createElement('div');
box.id = 'nck-main';
const pos = GM_getValue('nck_v378_pos', { x: window.innerWidth - 240, y: 50 });
box.style.left = pos.x + 'px'; box.style.top = pos.y + 'px';
box.innerHTML = `
<div id="nck-panel-container">
<div id="nck-panel">
<div class="panel-header"><span class="panel-ver">IRONLOCK v37.5.31.beta</span><div id="ip-skk" class="ip-link-btn">IP</div></div>
<div class="ip-row">
<div class="ip-row-info"><span class="ip-label">CN <b>IPIP</b></span><span id="ip-cn-ms-label" class="ip-ms ms-cn">--</span></div>
<span id="ip-cn-val" class="ip-val">...</span>
</div>
<div class="ip-row">
<div class="ip-row-info"><span class="ip-label">GLB <b>CF</b></span><span id="ip-gl-ms-label" class="ip-ms ms-gl">--</span></div>
<span id="ip-gl-val" class="ip-val">...</span>
</div>
<div id="matrix-area"><div class="ms-grid" id="grid-1"></div></div>
<div class="btn-group">
<div class="t-btn" id="t-matrix"></div><div class="t-btn" id="t-bayOn"></div>
<div class="t-btn" id="t-longShow"></div><div class="t-btn" id="t-locked"></div>
<div class="t-btn" id="t-bindCtrl"></div><div class="t-btn" id="t-pingShow"></div>
<div class="t-btn" id="t-lang"></div><div class="t-btn" id="t-hide"></div>
</div>
</div>
</div>
<div id="nck-trigger"><div id="led-cn" class="nck-led"></div><div id="nck-info-box"></div><div id="led-gl" class="nck-led"></div></div>
<div id="nck-bay"><div class="bay-play"></div></div>`;
document.body.appendChild(box);
const configKeys = ['matrix', 'bayOn', 'longShow', 'locked', 'bindCtrl', 'pingShow'];
configKeys.forEach(key => {
document.getElementById('t-' + key).onclick = (e) => {
e.preventDefault(); e.stopPropagation();
state.conf[key] = !state.conf[key];
GM_setValue('nck_v378_' + key, state.conf[key]);
syncUI();
};
});
document.getElementById('t-lang').onclick = (e) => {
e.preventDefault(); e.stopPropagation();
state.conf.lang = state.conf.lang === 'zh' ? 'en' : 'zh';
GM_setValue('nck_v378_lang', state.conf.lang);
syncUI();
};
// 最终隐藏修复
document.getElementById('t-hide').onclick = function(e) {
e.preventDefault();
e.stopPropagation();
console.log('[NCK] 执行隐藏指令');
state.conf.visible = false;
GM_setValue('nck_v378_vis', false);
syncUI();
};
document.getElementById('ip-skk').onclick = () => GM_openInTab("https://ip.skk.moe/multi", { active: true });
document.getElementById('nck-trigger').onclick = (e) => { e.stopPropagation(); checkNet(); };
document.getElementById('nck-bay').onclick = (e) => { e.stopPropagation(); handleSend(); };
box.onmousedown = (e) => {
if (e.target.closest('#nck-panel') || state.conf.locked || e.target.closest('#nck-bay')) return;
let ox = e.clientX - box.offsetLeft, oy = e.clientY - box.offsetTop;
const move = (ev) => {
box.style.left = ev.clientX - ox + 'px';
box.style.top = ev.clientY - oy + 'px';
updatePanelPos();
};
const stop = () => {
document.removeEventListener('mousemove', move);
GM_setValue('nck_v378_pos', { x: box.offsetLeft, y: box.offsetTop });
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', stop);
};
syncUI();
checkNet();
}
window.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter' && state.conf.bindCtrl) {
const activeEl = document.activeElement;
if (activeEl && (activeEl.tagName === 'TEXTAREA' || activeEl.contentEditable === 'true' || activeEl.tagName === 'INPUT')) {
e.preventDefault();
e.stopImmediatePropagation();
handleSend();
}
}
}, true);
GM_registerMenuCommand("[NCK] 唤醒 / 显示界面", () => {
state.conf.visible = true;
GM_setValue('nck_v378_vis', true);
syncUI();
});
GM_registerMenuCommand("[NCK] 重置位置居中", () => {
state.conf.visible = true;
GM_setValue('nck_v378_vis', true);
centerBox();
syncUI();
});
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', inject); else inject();
})();