NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Cpolar 自动显示状态 & "tcp://" 链接自动转为 SSH命令 & 自动刷新
// @namespace http://tampermonkey.net/
// @version 1.2
// @description 专为Cpolar SSH远程调试用户打造
// @author wujinjun
// @license MIT
// @match *://dashboard.cpolar.com/get-started
// @match *://dashboard.cpolar.com/status
// @grant GM_setClipboard
// ==/UserScript==
(function() {
'use strict';
// === 配置区域 ===
const USERNAME = "runner"; // 在此修改您的 SSH 用户名
const AUTO_JUMP_DELAY = 500; // 自动跳转延迟 (ms)
const FEEDBACK_DURATION = 1500; // 复制反馈显示时间 (ms)
const AUTO_REFRESH = 60; // 自动刷新时间 (秒),0 为不刷新
const KNOWN_HOSTS_IGNORE = true // 忽略 "known_hosts" 检查 (免费版 Cpolar 会随机生成域名和端口,忽略后可以避免每次重新输入 "yes")
const KNOWN_HOSTS_IGNORE_FILE = "" // 开启 `KNOWN_HOSTS_IGNORE` 之后,应该将 "known_hosts" 文件指向哪里 (如果不想保留,Linux设置为"/dev/null",Windows设置为用户临时目录,或创建内存盘(Ramdisk)后设置为内存盘内路径 (例如"R:\Temp\known_hosts") ("\"须转义,写作"\\"))
// === 1. 自动刷新功能 ===
if (AUTO_REFRESH > 0) {
// 仅在非 /get-started 页面刷新,避免干扰跳转逻辑
if (window.location.pathname !== '/get-started') {
setTimeout(() => {
location.reload();
}, AUTO_REFRESH * 1000);
console.log(`[脚本] 已设置 ${AUTO_REFRESH} 秒后自动刷新`);
}
}
// === 2. 自动导航功能 ===
if (window.location.pathname === '/get-started') {
setTimeout(() => {
window.location.href = '/status';
}, AUTO_JUMP_DELAY);
}
// === 3. SSH 转换逻辑 ===
function convertTcpToSsh(tcpUrl) {
// 匹配格式 tcp://domain.com:port
const regex = /tcp:\/\/([^:]+):(\d+)/;
const match = tcpUrl.match(regex);
if (match) {
const [_, host, port] = match;
//return `ssh -p ${port} ${USERNAME}@${host}`;
let ssh_string = `ssh -p ${port} `
if (KNOWN_HOSTS_IGNORE) {
ssh_string += "-o StrictHostKeyChecking=no "
}
if (KNOWN_HOSTS_IGNORE_FILE) {
ssh_string += `-o UserKnownHostsFile="${KNOWN_HOSTS_IGNORE_FILE}" `
}
ssh_string += `${USERNAME}@${host}`
return ssh_string
}
return null;
}
// === 4. 复制功能 (含反馈 UI) ===
async function copyToClipboard(text, anchor) {
// 创建反馈元素
const feedback = document.createElement('div');
Object.assign(feedback.style, {
position: 'fixed',
top: '20px',
right: '20px',
padding: '10px 15px',
borderRadius: '5px',
zIndex: '9999',
fontSize: '14px',
fontWeight: 'bold',
color: '#fff',
display: 'flex',
alignItems: 'center',
boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
transition: 'opacity 0.3s'
});
const handleSuccess = () => {
feedback.style.backgroundColor = '#28a745';
feedback.innerHTML = '<span><span style="margin-right:8px">✓</span>已复制</span>';
document.body.appendChild(feedback);
};
const handleFail = () => {
feedback.style.backgroundColor = '#dc3545';
feedback.innerHTML = '<span><span style="margin-right:8px">✕</span>失败</span>';
document.body.appendChild(feedback);
};
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
handleSuccess();
} else {
GM_setClipboard(text); // 备选方案
handleSuccess();
}
} catch (err) {
handleFail();
}
// 1.5秒后移除
setTimeout(() => {
feedback.style.opacity = '0';
setTimeout(() => feedback.remove(), 300);
}, FEEDBACK_DURATION);
}
// === 5. 处理链接的核心函数 ===
function processLinks() {
// 匹配 dashboard 路径下的 a 链接
const selector = "#dashboard > div > div:nth-child(3) > div.span9 > table > tbody > tr th > a";
const links = document.querySelectorAll(selector);
links.forEach(link => {
if (link.dataset.sshProcessed) return;
const rawText = link.innerText.trim();
if (rawText.startsWith('tcp://')) {
const sshCommand = convertTcpToSsh(rawText);
if (sshCommand) {
link.dataset.sshProcessed = "true";
link.style.cursor = "pointer";
link.style.textDecoration = "underline";
link.title = `点击复制: ${sshCommand}`;
// 悬停变色效果
link.style.transition = "color 0.2s";
link.onmouseenter = () => { link.style.color = "#007bff"; };
link.onmouseleave = () => { link.style.color = ""; };
link.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
copyToClipboard(sshCommand, link);
};
}
}
});
}
// === 6. 监听 DOM 变化 (MutationObserver) ===
const observer = new MutationObserver((mutations) => {
// 简单防抖,避免频繁触发
processLinks();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// 初次运行
processLinks();
})();