wujinjun / Cpolar 自动显示状态 & "tcp://" 链接自动转为 SSH命令 & 自动刷新

// ==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();
})();