弹幕留言

通过代码实现匿名留言并引用弹幕视频播放。

─=≡Σ((( つ•̀ω•́)つ

<?php
/**
 * ==========================================
 * 🛠️ 赛博风 Web 端引擎 - 共享本地 CSV 吐槽墙
 * ==========================================
 */
date_default_timezone_set('Asia/Shanghai');

// ✨ 吐槽墙 CSV 存储初始化
// 从当前目录往上跳3层,到达根目录
$csvFile = __DIR__ . 'comments.csv';
if (!file_exists($csvFile)) {
    @file_put_contents($csvFile, "IP,Time,Content\n", LOCK_EX);
}

// 🛡️ 防盗刷记录文件初始化
$ipRecordFile = __DIR__ . 'ip_records.json';

function sendJson($data, $status = 200) {
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}

$apiParam = isset($_GET['api']) ? $_GET['api'] : null;

// 🚀 独立 API 模块:获取留言
if ($apiParam === 'messages_list') {
    try {
        if (!file_exists($csvFile)) {
            sendJson(['ok' => true, 'data' => ['items' => []]]);
        }
        $lines = file($csvFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        
        // 修复:增加 is_array 判断,防止 PHP8 下极端并发读取时报错
        if (is_array($lines) && count($lines) > 0) {
            array_shift($lines); // 移除表头
        } else {
            $lines = [];
        }
        
        $lastLines = array_reverse(array_slice($lines, -20));
        $items = [];
        
        foreach ($lastLines as $line) {
            if (preg_match('/^([^,]+),([^,]+),"(.*)"$/', $line, $match)) {
                $items[] = [
                    'ip'         => $match[1],
                    'created_at' => $match[2],
                    'content'    => str_replace('""', '"', $match[3]),
                    'username'   => '吐槽墙' 
                ];
            } else {
                $items[] = ['content' => "解析异常记录", 'created_at' => date('Y/m/d H:i:s')];
            }
        }
        sendJson(['ok' => true, 'data' => ['items' => $items]]);
    } catch (Exception $e) {
        sendJson(['ok' => false, 'error' => '读取失败'], 500);
    }
}

// 🚀 独立 API 模块:发布留言 (🛡️ 已融合防盗刷拦截系统)
if ($apiParam === 'messages_create') {
    try {
        // 1. 获取客户端真实 IP 与时间戳
        $rawIp = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'unknown');
        $ip = trim(explode(',', $rawIp)[0]);
        $currentTime = time(); 

        // 2. 防盗刷校验逻辑
        $ipData = [];
        if (file_exists($ipRecordFile)) {
            $ipData = json_decode(file_get_contents($ipRecordFile), true) ?: [];
        }

        if (isset($ipData[$ip])) {
            // 如果已在黑名单,直接拦截
            if (!empty($ipData[$ip]['banned'])) {
                sendJson(['ok' => false, 'error' => 'SYSTEM LOCKDOWN: 您的 IP 触发防刷机制,已被永久封禁。'], 403);
            }
            
            // 判断连发间隔(<3秒直接封禁)
            $timeDiff = $currentTime - $ipData[$ip]['last_time'];
            if ($timeDiff < 1) {
                $ipData[$ip]['banned'] = true;
                file_put_contents($ipRecordFile, json_encode($ipData, JSON_UNESCAPED_UNICODE), LOCK_EX);
                sendJson(['ok' => false, 'error' => 'TRANSMISSION REJECTED: 发送频率过快,IP 已被封禁!'], 403);
            }
        }

        // 3. 校验通过,记录本次发送时间
        $ipData[$ip] = [
            'last_time' => $currentTime,
            'banned' => false
        ];
        file_put_contents($ipRecordFile, json_encode($ipData, JSON_UNESCAPED_UNICODE), LOCK_EX);

   // 4. 解析前端内容并写入 CSV
$rawInput = json_decode(file_get_contents('php://input'), true);
$rawContent = isset($rawInput['content']) ? trim($rawInput['content']) : '';

if (!$rawContent) {
    sendJson([
        'ok' => false,
        'error' => '内容不能为空'
    ]);
}


// =======================================
// 🚫 关键词屏蔽系统
// =======================================

$blockedWords = [
    // 广告推广
    '加微信',
    '微信号',
    'VX',
    'v信',
    'QQ号',
    '联系方式',
    '代理',
    '招商',

    // 常见垃圾
    '免费领取',
    '点击领取',
    '扫码',
    '豆包'
];

foreach ($blockedWords as $word) {

    if (mb_stripos($rawContent, $word, 0, 'UTF-8') !== false) {

        sendJson([
            'ok' => false,
            'error' => '内容包含禁止关键词,请修改后提交'
        ], 403);

    }
}


// =======================================
// 📝 写入 CSV
// =======================================

$timeStr = date('Y/m/d H:i:s');

$cleanContent = str_replace(
    '"',
    '""',
    preg_replace('/\r?\n/', ' ', $rawContent)
);


$csvLine = "{$ip},{$timeStr},\"{$cleanContent}\"\n";

file_put_contents(
    $csvFile,
    $csvLine,
    FILE_APPEND | LOCK_EX
);


sendJson([
    'ok' => true
]);
      
      

        $timeStr = date('Y/m/d H:i:s');
        $cleanContent = str_replace('"', '""', preg_replace('/\r?\n/', ' ', $rawContent));
        
        $csvLine = "{$ip},{$timeStr},\"{$cleanContent}\"\n";
        file_put_contents($csvFile, $csvLine, FILE_APPEND | LOCK_EX);
        sendJson(['ok' => true]);
    } catch (Exception $e) {
        sendJson(['ok' => false, 'error' => '写入失败'], 500);
    }
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <meta name="referrer" content="no-referrer">
    <meta name="referrer" content="strict-origin-when-cross-origin">
    <title>─=≡Σ((( つ•̀ω•́)つ</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script>
    <script>LA.init({id:"JRHGRBPWC7lJIaXq",ck:"JRHGRBPWC7lJIaXq"})</script>
    
   <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
    <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Share+Tech+Mono&family=Rajdhani:wght@500;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

    <style>
        :root { 
            --bg-deep: #030303; 
            --bg-panel: #0a0a0a;
            --accent: #00ff41; 
            --accent-dim: rgba(0, 255, 65, 0.1);
            --danger: #ff2a2a;
            --tech-blue: #00f0ff;
            --border-color: #333;
        }
        
        body { 
            font-family: 'Share Tech Mono', monospace; 
            background-color: var(--bg-deep); 
            color: #d4d4d4; 
            overflow: hidden; 
            height: 100dvh;
            background-image: 
                linear-gradient(rgba(0, 255, 65, 0.03) 1px, transparent 1px),
                linear-gradient(90deg, rgba(0, 255, 65, 0.03) 1px, transparent 1px);
            background-size: 40px 40px;
        }

        .scanlines {
            position: fixed; inset: 0; pointer-events: none; z-index: 2;
            background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1));
            background-size: 100% 4px;
        }
        
        .tech-border {
            position: relative;
            background: rgba(10, 10, 10, 0.8);
            backdrop-filter: blur(10px);
            border: 1px solid var(--border-color);
            box-shadow: 0 0 30px rgba(0,0,0,0.5);
            border-radius: 4px;
        }
        .tech-border::before { content: ''; position: absolute; top: -1px; left: -1px; width: 8px; height: 8px; border-top: 2px solid var(--accent); border-left: 2px solid var(--accent); border-top-left-radius: 4px; }
        .tech-border::after { content: ''; position: absolute; bottom: -1px; right: -1px; width: 8px; height: 8px; border-bottom: 2px solid var(--accent); border-right: 2px solid var(--accent); border-bottom-right-radius: 4px; }

        .embedded-drawer {
            background: #050505;
            border: 1px solid var(--tech-blue);
            border-radius: 12px;
            display: flex;
            flex-direction: column;
            height: 100%;
            box-shadow: 0 0 40px rgba(0, 0, 0, 0.8);
            position: relative;
            overflow: hidden;
        }

        .cyber-btn {
            position: relative;
            background: rgba(255,255,255,0.03);
            border: 1px solid var(--border-color);
            color: #888;
            font-family: 'Rajdhani', sans-serif;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 2px;
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
            overflow: hidden;
            border-radius: 4px; 
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
        }
        .cyber-btn:hover:not(:disabled) {
            background: var(--accent-dim); border-color: var(--accent); color: var(--accent);
            box-shadow: 0 0 15px var(--accent-dim); text-shadow: 0 0 5px var(--accent);
        }

        .monitor-frame {
            position: relative; width: 100%; height: 100%; background: #000; border: 1px solid #333;
            border-radius: 4px; overflow: hidden;
        }
        .monitor-overlay {
            position: absolute; inset: 0; pointer-events: none; z-index: 5;
            box-shadow: inset 0 0 100px rgba(0,0,0,0.9); border: 1px solid rgba(255,255,255,0.1);
            border-radius: 4px;
        }
        .monitor-label {
            position: absolute; top: 10px; left: 10px; z-index: 10;
            background: rgba(0,0,0,0.8); padding: 2px 8px;
            font-size: 10px; color: var(--accent); border: 1px solid var(--accent); border-radius: 2px;
        }
        .bili-full { position: absolute; inset: 0; width: 100%; height: 100%; }

        .cyber-scroll::-webkit-scrollbar { width: 4px; }
        .cyber-scroll::-webkit-scrollbar-thumb { background: var(--tech-blue); }

        .no-scrollbar::-webkit-scrollbar { display: none; }
        .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
        
        .cyber-input { 
            background: #000; border: 1px solid #333; color: var(--tech-blue); 
            font-family: 'Share Tech Mono'; width: 100%; transition: 0.3s; border-radius: 6px;
        }
        .cyber-input:focus { outline: none; border-color: var(--tech-blue); box-shadow: 0 0 10px rgba(0,240,255,0.2); }

        .nav-link:hover { color: var(--tech-blue); text-shadow: 0 0 5px var(--tech-blue); }
        .nav-link i { margin-right: 4px; }
        
        #delay a { 
            color: var(--tech-blue) !important; 
            text-decoration: none !important; 
            display: flex !important; 
            align-items: center !important; 
            height: 100% !important; 
            margin: 0 !important; 
            padding: 0 !important; 
            line-height: 1 !important; 
        }
        #delay a:hover { text-shadow: 0 0 5px var(--tech-blue); }
    </style>
</head>
<body class="flex flex-col h-[100dvh] overflow-hidden relative selection:bg-green-500 selection:text-black">

    <div class="scanlines"></div>

    <header class="hidden lg:flex h-12 bg-black/80 border-b border-[#222] items-center justify-between px-6 z-20 shrink-0 backdrop-blur-sm">
        <div class="flex items-center gap-3">
            <i class="fas fa-biohazard text-green-500 animate-pulse text-lg"></i>
            <span class="text-xl font-[Orbitron] font-black text-white tracking-widest">
                ─=≡Σ<span class="text-green-500">((( つ•̀ω•́)つ</span>
            </span>
        </div>
        
        <div class="hidden md:flex gap-6 text-[10px] font-mono text-gray-500 tracking-wider">
      
          <!-- ==================== 1. 网络工具与基础空间 ==================== -->

<!-- Google Labs Flow:AI 实验室/灵感流,魔术棒(如支持 FA6 可改用 fa-wand-magic-sparkles) -->
<a href="https://labs.google/fx/zh/tools/flow" target="_blank" class="nav-link transition"><i class="fas fa-magic"></i>BANANA-PRO</a>         
          
<!-- LLM Benchmark:大模型硬核跑分,使用微芯片图标,直击模型底层算力和架构跑分 -->
<a href="https://chatgpt.com/codex/cloud" target="_blank" class="nav-link transition"><i class="fas fa-microchip"></i>Codex</a>
          
<a href="https://chatgpt.com/codex/settings/usage" target="_blank" class="nav-link transition">
    <i class="fas fa-code"></i> Usage
</a>
     
<!-- ==================== 2. 开发、算力与 Token 管理 ==================== -->
<!-- Cloud Studio:云端代码空间 -->
<a href="https://cloudstudio.net/" target="_blank" class="nav-link transition"><i class="fas fa-laptop-code"></i>STUDIO</a>



  <a href="https://www.photopea.com/" target="_blank" class="nav-link transition">
    <i class="fas fa-image"></i> Photopea
</a>

<a href="https://opencut.app/projects" target="_blank" class="nav-link transition">
    <i class="fas fa-film"></i> OpenCut
</a>


<a href="http://cs1.bbn.com.cn:8800/gzweb/" target="_blank" class="nav-link transition">
    <i class="fas fa-desktop"></i> GZWeb
</a>        
          


<!-- Poixe:Token/密钥日志管理,使用密匙图标 -->
<a href="https://poixe.com/pricing?i=token" target="_blank" class="nav-link transition"><i class="fas fa-key"></i>Poixe</a>

<!-- Gemini AI:相比传统的旧版 robot,使用 brain (大脑) 更符合 AGI/大模型时代 -->
<a href="https://apimart.ai/register?aff=token" target="_blank" class="nav-link transition" title="Gemini"><i class="fas fa-brain"></i>APIMart</a>

          
        </div>

        <div class="text-[10px] font-mono text-gray-500 flex items-center gap-2">
            <span class="w-1.5 h-1.5 rounded-full bg-green-500 box-shadow-green"></span>
            <span>SYSTEM: ONLINE</span>
        </div>
    </header>

    <div class="flex-grow flex flex-col lg:flex-row overflow-hidden relative z-10 p-2 lg:p-4 gap-2 lg:gap-4 min-h-0">
        
        <div class="h-[35vh] lg:h-auto lg:w-[65%] xl:w-[70%] flex flex-col gap-2 shrink-0 lg:shrink min-h-0">
            <div class="monitor-frame relative flex-grow tech-border min-h-0">
                
                <div class="absolute top-2 right-2 z-20 flex items-center gap-2">
                    <a id="bili-source-btn" href="#" target="_blank" title="在新窗口打开原视频" 
                       class="flex items-center gap-2 px-2 py-1 bg-black/80 border border-gray-700 rounded text-[10px] font-mono text-gray-400 hover:text-[#00f0ff] hover:border-[#00f0ff] transition backdrop-blur-sm group">
                        <i class="fab fa-bilibili"></i>
                        <span>SOURCE</span>
                    </a>
                </div>

                <div class="monitor-overlay"></div>
                
                <div id="video-container" class="w-full h-full relative z-0">
                    <div class="absolute inset-0 flex items-center justify-center text-gray-700 animate-pulse font-mono text-xs">
                        ESTABLISHING UPLINK...
                    </div>
                </div>
            </div>
            
            <div class="h-10 tech-border flex items-center justify-between px-4 bg-black/40 shrink-0">
                <button id="btn-prev" onclick="changePage(-1)" class="text-xs text-gray-500 hover:text-white transition">
                    <i class="fas fa-caret-left mr-1"></i> PREV
                </button>
                <div class="text-xs font-mono text-gray-600">
                    FILE_INDEX: <span id="page-indicator" class="text-white">00 / 00</span>
                </div>
                <button id="btn-next" onclick="changePage(1)" class="text-xs text-gray-500 hover:text-white transition">
                    NEXT <i class="fas fa-caret-right ml-1"></i>
                </button>
            </div>
        </div>

        <div class="flex-1 lg:flex-none lg:w-[35%] xl:w-[30%] flex flex-col min-h-0 relative">
            
            <div class="embedded-drawer w-full h-full relative overflow-hidden">
                
                <div class="p-2 sm:p-3 border-b border-gray-800 flex flex-nowrap justify-between items-center gap-2 shrink-0 bg-[#050505] z-20">
                    
                    <div class="flex-1 flex text-[10px] font-mono opacity-90 items-stretch h-[26px] text-[#00f0ff] overflow-hidden">
                        <div class="flex-1 border border-gray-700 border-r-0 rounded-l bg-black/60 overflow-x-auto no-scrollbar flex items-center px-1.5 sm:px-2 min-w-0">
                            <span id="delay" class="whitespace-nowrap flex items-center h-full">
                                <script id="LA-DATA-WIDGET" crossorigin="anonymous" charset="UTF-8" src="https://v6-widget.51.la/v6/JRHGRBPWC7lJIaXq/quote.js?theme=0&f=12&display=0,0,1,1,0,0,0,0"></script>
                            </span>
                        </div>
                        <span id="performance-result" class="px-1.5 sm:px-2 border border-gray-700 rounded-r flex items-center whitespace-nowrap bg-black/60 shrink-0">
                            加载:--ms
                        </span>
                    </div>
                    
                    <div class="flex items-center bg-[#0a0a0a] border border-gray-700 rounded text-[10px] shadow-[0_0_10px_rgba(0,0,0,0.5)] shrink-0">
                        <button onclick="manualRefresh()" class="px-2.5 py-1 text-gray-400 hover:text-[#00f0ff] hover:bg-[rgba(0,240,255,0.1)] transition-all cursor-pointer group" title="同步最新数据">
                            <i class="fas fa-sync-alt group-hover:rotate-180 transition-transform duration-300" id="refresh-icon"></i>
                        </button>
                        <div class="w-px h-3.5 bg-gray-700"></div>
                        <button onclick="toggleExtensionFrame()" class="px-2.5 py-1 text-gray-400 hover:text-[#00ff41] hover:bg-[rgba(0,255,65,0.1)] transition-all cursor-pointer group" title="AIGC">
                            <i class="fas fa-satellite-dish group-hover:scale-110 transition-transform"></i>
                        </button>
                    </div>

                </div>
                
                <div id="comment-list" class="flex-grow overflow-y-auto p-4 space-y-6 cyber-scroll relative z-10 min-h-0">
                    <div class="text-center text-gray-600 text-xs mt-10 font-mono animate-pulse">DOWNLOADING PACKETS...</div>
                </div>
                
                <div class="p-3 lg:p-4 border-t border-gray-800 shrink-0 relative z-10 bg-[#050505]">
                    <form id="cyber-form" onsubmit="event.preventDefault(); postComment();" class="flex flex-col gap-2">
                        <input type="hidden" name="username" id="hidden-username" value="吐槽墙">
                        <div>
                            <textarea id="message" name="content" placeholder="Input data..." class="cyber-input px-3 py-2 text-[13px] h-16 lg:h-20 resize-none" required></textarea>
                        </div>
                        <button type="submit" id="send-btn" class="cyber-btn w-full py-2 lg:py-2.5 text-xs font-bold border-[#00f0ff] text-[#00f0ff] hover:bg-[#00f0ff] hover:text-black shrink-0">
                            TRANSMIT
                        </button>
                    </form>
                </div>

                <div id="extension-drawer" class="absolute inset-0 z-50 bg-[#050505] flex flex-col transform translate-x-full transition-transform duration-300 ease-in-out border-l border-[#00ff41]">
                    <div class="p-2 sm:p-3 border-b border-[#00ff41]/50 flex justify-between items-center bg-black/90 backdrop-blur z-10 shrink-0 shadow-[0_0_15px_rgba(0,255,65,0.2)]">
                        <span class="text-[10px] font-mono text-[#00ff41] tracking-widest flex items-center">
                            <i class="fas fa-network-wired mr-2 animate-pulse"></i>
                            TERMINAL_UPLINK
                        </span>
                        <button onclick="toggleExtensionFrame()" class="text-gray-400 hover:text-[#ff2a2a] transition-colors px-2 cursor-pointer outline-none">
                            <i class="fas fa-times text-lg"></i>
                        </button>
                    </div>
                    <iframe id="ext-iframe" src="https://tcq233.com/share/" class="flex-grow w-full h-full border-none bg-black" sandbox="allow-scripts allow-same-origin allow-popups allow-forms allow-downloads"
                  

            allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
            loading="lazy"
          ></iframe>
                </div>

            </div>

        </div>
    </div>

<script>
        const API_BASE = "/page/?api=";

        let database = [];
        let currentIndex = 0;
        let latestCommentId = null; 

        window.addEventListener('load', function () {
            const elapsed = Math.round(performance.now());
            const result = document.getElementById('performance-result');
            if (result) {
                result.innerHTML = '加载:' + elapsed + 'ms';
            }
        });

        window.onload = async () => {
            await loadDatabase();
            
            if (database.length > 0) {
                loadCase(0);
            }
            
            loadComments();
            
            // 后台静默轮询
            setInterval(() => {
                loadComments(true); 
            }, 10000);
            
            document.addEventListener('keydown', (e) => {
                if(e.key === 'ArrowRight') changePage(1);
                if(e.key === 'ArrowLeft') changePage(-1);
            });
        };

        // --- 拓展面板切换逻辑 ---
        function toggleExtensionFrame() {
            const drawer = document.getElementById('extension-drawer');
            if (drawer.classList.contains('translate-x-full')) {
                // 滑出面板
                drawer.classList.remove('translate-x-full');
            } else {
                // 收回面板
                drawer.classList.add('translate-x-full');
            }
        }

        // --- 纯净版加载 TXT 配置:仅支持 BV 号 ---
        async function loadDatabase() {
            try {
                const res = await fetch('videos.txt?t=' + new Date().getTime());
                if (!res.ok) throw new Error('找不到 videos.txt 文件');
                
                const text = await res.text();
                const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0);
                
                database = lines.map((line, index) => {
                    let target = line;
                    
                    const bvMatch = line.match(/(BV[a-zA-Z0-9]+)/);
                    if (bvMatch) {
                        target = bvMatch[1];
                    } 

                    return { 
                        id: String(index + 1).padStart(2, '0'), 
                        src: target, 
                        desc: `TARGET_ID: ${target}` 
                    };
                });

                if (database.length === 0) throw new Error('txt文件是空的');
                
            } catch (err) {
                console.warn('读取 txt 失败,使用内置默认数据', err);
                database = [
                    { id: '01', src: 'BV1Y34y1X7CK', desc: 'TARGET_ID: BV1Y34y1X7CK' },
                    { id: '02', src: 'BV1My2YBsEcJ', desc: 'TARGET_ID: BV1My2YBsEcJ' } 
                ];
            }
        }

        function changePage(direction) {
            if (database.length === 0) return;
            let newIndex = currentIndex + direction;
            if (newIndex >= database.length) newIndex = 0;
            if (newIndex < 0) newIndex = database.length - 1;
            loadCase(newIndex);
        }

        function loadCase(index) {
            if (database.length === 0) return;
            currentIndex = index;
            const currentData = database[currentIndex];
            
            document.getElementById('page-indicator').innerText = `${String(index + 1).padStart(2, '0')} / ${String(database.length).padStart(2, '0')}`;
            
            const sourceBtn = document.getElementById('bili-source-btn');
            if (sourceBtn) {
                sourceBtn.href = `https://www.bilibili.com/video/${currentData.src}`;
            }

            const container = document.getElementById('video-container');
            container.innerHTML = `<iframe src="//player.bilibili.com/player.html?bvid=${currentData.src}&page=1&high_quality=1&danmaku=0&autoplay=1" class="bili-full" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" sandbox="allow-top-navigation allow-same-origin allow-forms allow-scripts allow-popups"></iframe>`;
        }

        function escapeHtml(s) {
            if (!s) return '';
            return s.replace(/[&<>"']/g, m => ({
                '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
            }[m]));
        }

        async function manualRefresh() {
            const icon = document.getElementById('refresh-icon');
            icon.classList.add('fa-spin'); 
            
            await loadComments(false, true); 
            
            const list = document.getElementById('comment-list');
            list.scrollTop = 0;
            
            setTimeout(() => {
                icon.classList.remove('fa-spin');
            }, 600);
        }

       async function loadComments(isAutoRefresh = false, force = false) {
            const list = document.getElementById('comment-list');
            
            if (!isAutoRefresh && list.innerHTML.includes('DOWNLOADING')) {
                 list.innerHTML = '<div class="text-center text-gray-600 text-xs mt-10 font-mono animate-pulse">DOWNLOADING PACKETS...</div>';
            }

            try {
                const res = await fetch(`${API_BASE}messages_list&_t=${new Date().getTime()}`);
                const json = await res.json();
                
                if (json.ok) {
                    const items = json.data.items;

                    if (!items || items.length === 0) {
                        if (!isAutoRefresh) list.innerHTML = '<div class="text-center text-gray-600 text-xs mt-10 font-mono">NO LOGS FOUND</div>';
                        return;
                    }
                    
                    if (isAutoRefresh && !force && latestCommentId === items[0].id) {
                        return;
                    }
                    
                    latestCommentId = items[0].id;
                    list.innerHTML = ''; 
                    
                    items.forEach(item => {
                        const div = document.createElement('div');
                        div.className = 'group mb-4'; 
                        
                        // 🛠️ 智能提取 IP (兼容 IPv4、IPv6 及本地测试环境)
                        let rawIp = item.ip || "未知";
                        let maskedIp = "未知";
                        
                        if (rawIp.includes('.')) {
                            // IPv4 格式:192.168.1.100 -> 192.168.1.*
                            let parts = rawIp.split('.');
                            maskedIp = parts.length >= 3 ? parts.slice(0, 3).join('.') + '.*' : rawIp;
                        } else if (rawIp.includes(':')) {
                            // IPv6 格式:2408:8207:abcd:... -> 2408:8207:abcd:*
                            let parts = rawIp.split(':');
                            maskedIp = parts.length >= 3 ? parts.slice(0, 3).join(':') + ':*' : rawIp;
                        } else {
                            // 兜底处理 (比如代理传回来的 unknown)
                            maskedIp = rawIp; 
                        }
                        
                        // ⬅️ 直接使用 IP 作为显示名称,去掉“吐槽墙”后缀
                        let displayName = maskedIp;
                        let nameStyle = "text-[14px] font-bold text-gray-300 group-hover:text-[#00f0ff] transition-colors";

                        // 特殊高亮逻辑 (保留你的站长专属标识)
                        if (item.ip && item.ip.includes('43.162.119')) {
                            displayName = "0.* 站长专属"; 
                            nameStyle = "text-[14px] font-bold text-[#ff2a2a] group-hover:text-[#ff5555] drop-shadow-[0_0_8px_rgba(255,42,42,0.8)] transition-colors tracking-wider";
                        } else if (item.username === 'AI小助手') {
                            displayName = `${maskedIp} AI小助手`;
                            nameStyle = "text-[14px] font-bold text-[#ff2a2a] group-hover:text-[#ff5555] drop-shadow-[0_0_8px_rgba(255,42,42,0.8)] transition-colors tracking-wider";
                        }
                        
                        div.innerHTML = `
                            <div class="flex justify-between items-baseline mb-1">
                                <span class="${nameStyle}">${escapeHtml(displayName)}</span>
                                <span class="text-[10px] text-gray-600 font-mono">${timeAgo(item.created_at)}</span>
                            </div>
                            <div class="text-[13px] text-gray-400 break-words leading-relaxed">${escapeHtml(item.content)}</div>
                        `;
                        list.appendChild(div);
                    });
                } else {
                    if (!isAutoRefresh) list.innerHTML = `<div class="text-red-500 text-xs font-mono text-center">ERROR: ${json.error}</div>`;
                }
            } catch (err) {
                console.error(err);
                if (!isAutoRefresh) list.innerHTML = '<div class="text-red-600 text-xs font-mono text-center">CONNECTION LOST</div>';
            }
        }

        async function postComment() {
            const msgBox = document.getElementById('message');
            const btn = document.getElementById('send-btn');
            const content = msgBox.value.trim();
            
            if (!content) return;
            
            btn.innerHTML = 'TRANSMITTING...';
            btn.disabled = true;

            try {
                const res = await fetch(`${API_BASE}messages_create`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ content: content })
                });
                
                const json = await res.json();
                
                if (json.ok) {
                    msgBox.value = '';
                    await loadComments(false, true); 
                    const list = document.getElementById('comment-list');
                    list.scrollTop = 0;
                } else {
                    alert('TRANSMISSION FAILED: ' + (json.error || 'Unknown Error'));
                }
            } catch (err) {
                alert('NETWORK ERROR: CANNOT REACH SERVER');
            } finally {
                btn.innerHTML = 'TRANSMIT';
                btn.disabled = false;
            }
        }

        function timeAgo(dateString) {
            if(!dateString) return '';
            const date = new Date(dateString.replace(/-/g, '/'));
            const seconds = Math.floor((new Date() - date) / 1000);
            if (seconds < 60) return 'NOW';
            const minutes = Math.floor(seconds / 60);
            if (minutes < 60) return minutes + 'M';
            const hours = Math.floor(minutes / 60);
            if (hours < 24) return hours + 'H';
            return Math.floor(hours / 24) + 'D';
        }
</script>
</body>
</html>