通过代码实现匿名留言并引用弹幕视频播放。
<?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 => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
}[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>