1.我们的Nmpostor服务器管理面板
https://auserverpanel.fanchuanovo.cn/
由清风AmongUs服务器的开源代码修改
2.我们的开源html Nmpostor AmongUs Server CDJ面板
https://aucdj.fanchuanovo.cn/
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>帆船Nmpostor车队姬管理面板</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2c3e50;
--accent-color: #e74c3c;
--success-color: #2ecc71;
--warning-color: #f39c12;
--dark-bg: #1a1d29;
--card-bg: #252836;
--card-hover: #2e3243;
--text-light: #f8f9fa;
--text-muted: #adb5bd;
--border-color: rgba(255, 255, 255, 0.1);
--input-bg: rgba(0, 0, 0, 0.3);
--table-header-bg: rgba(0, 0, 0, 0.2);
--table-striped-bg: rgba(255, 255, 255, 0.03);
--table-hover-bg: rgba(255, 255, 255, 0.08);
--header-bg: linear-gradient(135deg, rgba(52, 152, 219, 0.1), rgba(231, 76, 60, 0.1));
}
[data-theme="light"] {
--dark-bg: #f8f9fa;
--card-bg: #ffffff;
--card-hover: #f0f0f0;
--text-light: #212529;
--text-muted: #6c757d;
--border-color: rgba(0, 0, 0, 0.1);
--input-bg: rgba(0, 0, 0, 0.05);
--table-header-bg: rgba(0, 0, 0, 0.05);
--table-striped-bg: rgba(0, 0, 0, 0.02);
--table-hover-bg: rgba(0, 0, 0, 0.05);
--header-bg: linear-gradient(135deg, rgba(52, 152, 219, 0.05), rgba(231, 76, 60, 0.05));
}
body {
background-color: var(--dark-bg);
color: var(--text-light);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding-top: 20px;
min-height: 100vh;
transition: background-color 0.3s, color 0.3s;
}
.container {
max-width: 1200px;
}
.card {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s, background-color 0.3s, border-color 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--table-header-bg);
border-bottom: 1px solid var(--border-color);
padding: 15px 20px;
border-radius: 12px 12px 0 0 !important;
transition: background-color 0.3s, border-color 0.3s;
}
.card-header h5 {
margin: 0;
font-weight: 600;
color: var(--text-light);
}
.card-body {
padding: 20px;
}
.table {
color: var(--text-light);
margin-bottom: 0;
transition: color 0.3s;
}
.table th {
border-top: none;
background-color: var(--table-header-bg);
color: var(--text-muted);
font-weight: 600;
padding: 12px 15px;
transition: background-color 0.3s, color 0.3s;
}
.table td {
padding: 12px 15px;
border-color: var(--border-color);
vertical-align: middle;
transition: border-color 0.3s;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: var(--table-striped-bg);
transition: background-color 0.3s;
}
.table-hover tbody tr:hover {
background-color: var(--table-hover-bg);
transition: background-color 0.3s;
}
.nav-tabs {
border-bottom: 1px solid var(--border-color);
margin-bottom: 20px;
transition: border-color 0.3s;
}
.nav-tabs .nav-link {
color: var(--text-muted);
border: none;
padding: 12px 20px;
border-radius: 8px 8px 0 0;
margin-right: 5px;
transition: all 0.3s;
font-weight: 500;
}
.nav-tabs .nav-link:hover {
color: var(--text-light);
background-color: var(--table-striped-bg);
}
.nav-tabs .nav-link.active {
color: var(--primary-color);
background-color: var(--card-bg);
border-bottom: 2px solid var(--primary-color);
}
.form-control, .form-select {
background-color: var(--input-bg);
border: 1px solid var(--border-color);
color: var(--text-light);
border-radius: 6px;
padding: 10px 15px;
transition: background-color 0.3s, border-color 0.3s, color 0.3s;
}
.form-control:focus, .form-select:focus {
background-color: var(--input-bg);
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(52, 152, 219, 0.25);
color: var(--text-light);
}
.form-label {
color: var(--text-light);
font-weight: 500;
margin-bottom: 8px;
transition: color 0.3s;
}
.form-text {
color: var(--text-muted);
transition: color 0.3s;
}
.btn {
border-radius: 6px;
font-weight: 500;
padding: 8px 16px;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 5px;
}
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-primary:hover {
background-color: #2980b9;
border-color: #2980b9;
transform: translateY(-2px);
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
}
.btn-success {
background-color: var(--success-color);
border-color: var(--success-color);
}
.btn-warning {
background-color: var(--warning-color);
border-color: var(--warning-color);
color: white;
}
.btn-danger {
background-color: var(--accent-color);
border-color: var(--accent-color);
}
.btn-outline-secondary {
color: var(--text-muted);
border-color: var(--text-muted);
}
.btn-outline-secondary:hover {
background-color: var(--text-muted);
border-color: var(--text-muted);
color: var(--dark-bg);
}
.alert {
border-radius: 8px;
border: none;
}
.alert-info {
background-color: rgba(52, 152, 219, 0.2);
color: var(--text-light);
}
.modal-content {
background-color: var(--card-bg);
border-radius: 12px;
border: 1px solid var(--border-color);
transition: background-color 0.3s, border-color 0.3s;
}
.modal-header {
border-bottom: 1px solid var(--border-color);
transition: border-color 0.3s;
}
.modal-footer {
border-top: 1px solid var(--border-color);
transition: border-color 0.3s;
}
.modal-title {
color: var(--text-light);
transition: color 0.3s;
}
.form-check-input:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
header h1 {
color: var(--text-light);
font-weight: 700;
margin-bottom: 25px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: relative;
display: inline-block;
transition: color 0.3s;
}
header h1::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 100px;
height: 3px;
background: linear-gradient(to right, var(--primary-color), var(--accent-color));
border-radius: 3px;
}
.header-section {
background: var(--header-bg);
border-radius: 12px;
padding: 20px;
margin-bottom: 25px;
border: 1px solid var(--border-color);
transition: background 0.3s, border-color 0.3s;
}
footer {
margin-top: 40px;
padding: 20px 0;
border-top: 1px solid var(--border-color);
color: var(--text-muted);
transition: border-color 0.3s, color 0.3s;
}
footer a {
color: var(--text-muted);
text-decoration: none;
transition: color 0.3s;
}
footer a:hover {
color: var(--primary-color);
}
.dropdown-menu {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s, border-color 0.3s;
}
.dropdown-item {
color: var(--text-light);
transition: color 0.3s, background-color 0.3s;
}
.dropdown-item:hover {
background-color: var(--table-striped-bg);
color: var(--text-light);
}
.dropdown-item-text {
color: var(--text-muted);
}
.input-group-text {
background-color: var(--input-bg);
border: 1px solid var(--border-color);
color: var(--text-muted);
transition: background-color 0.3s, border-color 0.3s, color 0.3s;
}
.badge {
font-weight: 500;
padding: 6px 10px;
border-radius: 6px;
}
/* 主题切换按钮 */
.theme-switcher {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
.theme-switcher .btn {
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--table-striped-bg);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* 响应式调整 */
@media (max-width: 768px) {
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.card-header div {
width: 100%;
display: flex;
gap: 10px;
}
.card-header div .btn {
flex: 1;
}
.theme-switcher {
position: static;
margin-bottom: 20px;
display: flex;
justify-content: flex-end;
}
}
</style>
</head>
<body data-theme="dark">
<div class="theme-switcher">
<button class="btn btn-primary" id="themeToggle">
<i class="bi bi-sun" id="themeIcon"></i>
</button>
</div>
<div class="container">
<header class="mb-4 text-center">
<h1><i class="bi bi-speedometer2 me-2"></i>帆船Nmpostor车队姬管理面板</h1>
<div class="header-section">
<div class="row g-3 align-items-center">
<div class="col-md-5">
<label for="serverAddress" class="form-label"><i class="bi bi-server me-1"></i>API 服务器地址</label>
<div class="input-group">
<input type="text" class="form-control" id="serverAddress"
placeholder="例如: http://localhost:5000/api/cdj">
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
data-bs-toggle="dropdown" title="历史记录">
<i class="bi bi-clock-history"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end" id="historyDropdown">
<li><span class="dropdown-item-text text-muted">加载中...</span></li>
</ul>
</div>
</div>
</div>
<div class="col-md-5">
<label for="authToken" class="form-label"><i class="bi bi-key me-1"></i>认证 Token (可选)</label>
<input type="text" class="form-control" id="authToken" placeholder="如果需要认证,请在此处输入 Token">
</div>
<div class="col-md-2">
<label class="form-label"> </label>
<div class="d-grid">
<button class="btn btn-outline-danger btn-sm" onclick="clearAllHistory()" title="清除所有历史记录">
<i class="bi bi-trash"></i> 清除历史
</button>
</div>
</div>
</div>
</div>
</header>
<ul class="nav nav-tabs" id="mainTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="players-tab" data-bs-toggle="tab" data-bs-target="#players-tab-pane"
type="button" role="tab">
<i class="bi bi-people me-1"></i>玩家管理
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="onebot-tab" data-bs-toggle="tab" data-bs-target="#onebot-tab-pane"
type="button" role="tab">
<i class="bi bi-robot me-1"></i>OneBot 配置
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="templates-tab" data-bs-toggle="tab" data-bs-target="#templates-tab-pane"
type="button" role="tab">
<i class="bi bi-chat-text me-1"></i>消息模板
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="server-tab" data-bs-toggle="tab" data-bs-target="#server-tab-pane"
type="button" role="tab">
<i class="bi bi-gear me-1"></i>服务器设置
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="token-tab" data-bs-toggle="tab" data-bs-target="#token-tab-pane"
type="button" role="tab">
<i class="bi bi-shield-lock me-1"></i>Token 管理
</button>
</li>
</ul>
<div class="tab-content" id="mainTabContent">
<!-- 玩家管理 Tab -->
<div class="tab-pane fade show active" id="players-tab-pane" role="tabpanel">
<div class="card mt-3">
<div class="card-header">
<h5><i class="bi bi-people me-2"></i>玩家列表</h5>
<div>
<button class="btn btn-primary btn-sm" id="addPlayerBtn">
<i class="bi bi-plus-circle"></i> 添加玩家
</button>
<button class="btn btn-secondary btn-sm" id="refreshPlayersBtn">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>PUID</th>
<th>Token Platform</th>
<th>QQ ID</th>
<th>使用CDJ</th>
<th>OneBot 配置</th>
<th>操作</th>
</tr>
</thead>
<tbody id="playersTableBody">
<!-- 玩家数据将在这里动态生成 -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- OneBot 配置 Tab -->
<div class="tab-pane fade" id="onebot-tab-pane" role="tabpanel">
<div class="card mt-3">
<div class="card-header">
<h5><i class="bi bi-robot me-2"></i>OneBot 配置列表</h5>
<div>
<button class="btn btn-primary btn-sm" id="addOneBotConfigBtn">
<i class="bi bi-plus-circle"></i> 添加配置
</button>
<button class="btn btn-secondary btn-sm" id="refreshOneBotConfigsBtn">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>配置名称</th>
<th>启用状态</th>
<th>URL</th>
<th>类型</th>
<th>目标ID</th>
<th>操作</th>
</tr>
</thead>
<tbody id="oneBotConfigsTableBody">
<!-- OneBot 配置将在这里动态生成 -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 消息模板 Tab -->
<div class="tab-pane fade" id="templates-tab-pane" role="tabpanel">
<div class="card mt-3">
<div class="card-header">
<h5><i class="bi bi-chat-text me-2"></i>消息模板</h5>
<div>
<button class="btn btn-primary btn-sm" id="saveMessageTemplatesBtn">
<i class="bi bi-check-circle"></i> 保存更改
</button>
<button class="btn btn-secondary btn-sm" id="refreshMessageTemplatesBtn">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
</div>
</div>
<div class="card-body">
<form id="messageTemplatesForm">
<div class="mb-3">
<label for="templateRoomCreated" class="form-label">房间创建</label>
<textarea class="form-control" id="templateRoomCreated" name="RoomCreated"
rows="2"></textarea>
</div>
<div class="mb-3">
<label for="templateGameStarted" class="form-label">游戏开始</label>
<textarea class="form-control" id="templateGameStarted" name="GameStarted"
rows="2"></textarea>
</div>
<div class="mb-3">
<label for="templateGameEnded" class="form-label">游戏结束</label>
<textarea class="form-control" id="templateGameEnded" name="GameEnded"
rows="2"></textarea>
</div>
<div class="mb-3">
<label for="templateHostChanged" class="form-label">房主变更</label>
<textarea class="form-control" id="templateHostChanged" name="HostChanged"
rows="2"></textarea>
</div>
<div class="mb-3">
<label for="templateCdjAvailable" class="form-label">CDJ可用通知</label>
<textarea class="form-control" id="templateCdjAvailable" name="CdjAvailableNotice"
rows="2"></textarea>
</div>
</form>
</div>
</div>
</div>
<!-- 服务器设置 Tab -->
<div class="tab-pane fade" id="server-tab-pane" role="tabpanel">
<div class="card mt-3">
<div class="card-header">
<h5><i class="bi bi-gear me-2"></i>服务器名称</h5>
</div>
<div class="card-body">
<div class="input-group">
<input type="text" class="form-control" id="serverNameInput" placeholder="输入服务器名称">
<button class="btn btn-primary" id="updateServerNameBtn">
<i class="bi bi-check-lg"></i> 更新
</button>
<button class="btn btn-secondary" id="refreshServerNameBtn">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
</div>
</div>
</div>
</div>
<!-- Token 管理 Tab -->
<div class="tab-pane fade" id="token-tab-pane" role="tabpanel">
<div class="card mt-3">
<div class="card-header">
<h5><i class="bi bi-shield-lock me-2"></i>管理 Token</h5>
</div>
<div class="card-body">
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle me-2"></i>
<strong>提示:</strong>如果您是首次设置或忘记了当前 Token,可以在服务器配置文件 <code>Niko.CDJPluginConfig.json</code> 中查看初始 Token 设置。
</div>
<div class="mb-3">
<label for="currentTokenDisplay" class="form-label">当前 Token</label>
<div class="input-group">
<input type="password" class="form-control" id="currentTokenDisplay" readonly>
<button class="btn btn-outline-secondary" type="button" id="toggleCurrentTokenBtn" title="显示/隐藏">
<i class="bi bi-eye"></i>
</button>
</div>
<div class="form-text">当前正在使用的管理 Token</div>
</div>
<div class="mb-3">
<label for="newTokenInput" class="form-label">新 Token</label>
<div class="input-group">
<input type="text" class="form-control" id="newTokenInput" placeholder="输入新的管理 Token">
<button class="btn btn-success" id="generateTokenBtn" title="生成随机 Token">
<i class="bi bi-dice-5"></i> 生成随机 Token
</button>
</div>
<div class="form-text">留空将禁用 Token 验证,任何人都可以访问管理接口</div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<button class="btn btn-primary" id="updateTokenBtn">
<i class="bi bi-check-lg"></i> 更新 Token
</button>
<button class="btn btn-secondary" id="refreshTokenBtn">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
</div>
</div>
</div>
</div>
</div>
<footer class="text-center">
<p class="text-secondary small mb-2">
<i class="bi bi-c-circle me-1"></i>2025 By
<a href="https://fcaugame.cn" target="_blank"
class="link-secondary link-offset-1 link-underline-opacity-25 link-underline-opacity-100-hover">
帆船AmongUs服务器丨
</a>
<a href="https://fcaugame.cn" target="_blank"
class="link-secondary link-offset-1 link-underline-opacity-25 link-underline-opacity-100-hover">
当前版本:sabcdjwebui-2.0丨
</a>
<a href="https://beian.miit.gov.cn/" target="_blank">粤ICP备2025466846号-2</a>
</p>
</footer>
</div>
<!-- 玩家编辑/添加 Modal -->
<div class="modal fade" id="playerModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="playerModalTitle">玩家信息</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="playerForm">
<input type="hidden" id="player-original-puid">
<input type="hidden" id="player-original-tokenPlatform">
<div class="row">
<div class="col-md-6 mb-3">
<label for="player-puid" class="form-label">PUID</label>
<input type="text" class="form-control" id="player-puid" required>
</div>
<div class="col-md-6 mb-3">
<label for="player-tokenPlatform" class="form-label">Token Platform</label>
<input type="text" class="form-control" id="player-tokenPlatform" required>
</div>
</div>
<div class="mb-3">
<label for="player-qqId" class="form-label">QQ ID</label>
<input type="number" class="form-control" id="player-qqId" required>
</div>
<div class="mb-3">
<label for="player-oneBotConfigNames" class="form-label">OneBot 配置名称 (用逗号分隔)</label>
<input type="text" class="form-control" id="player-oneBotConfigNames">
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="player-announceRoomOnCreated">
<label class="form-check-label" for="player-announceRoomOnCreated">创建房间时通知</label>
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="player-announceGameStartEnd">
<label class="form-check-label" for="player-announceGameStartEnd">游戏开始/结束时通知</label>
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="player-useCDJ">
<label class="form-check-label" for="player-useCDJ">启用 CDJ 功能</label>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" id="savePlayerBtn">保存</button>
</div>
</div>
</div>
</div>
<!-- OneBot 配置编辑/添加 Modal -->
<div class="modal fade" id="oneBotConfigModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="oneBotConfigModalTitle">OneBot 配置</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="oneBotConfigForm">
<input type="hidden" id="onebot-original-name">
<div class="mb-3">
<label for="onebot-name" class="form-label">配置名称</label>
<input type="text" class="form-control" id="onebot-name" required>
</div>
<div class="mb-3">
<label for="onebot-url" class="form-label">URL</label>
<input type="text" class="form-control" id="onebot-url" required>
</div>
<div class="mb-3">
<label for="onebot-token" class="form-label">Token</label>
<input type="text" class="form-control" id="onebot-token">
</div>
<div class="mb-3">
<label for="onebot-type" class="form-label">类型</label>
<select class="form-select" id="onebot-type">
<option value="private">private</option>
<option value="group">group</option>
</select>
</div>
<div class="mb-3">
<label for="onebot-targetId" class="form-label">目标 ID</label>
<input type="number" class="form-control" id="onebot-targetId" required>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="onebot-enabled">
<label class="form-check-label" for="onebot-enabled">启用</label>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" id="saveOneBotConfigBtn">保存</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 主题切换功能
const themeToggle = document.getElementById('themeToggle');
const themeIcon = document.getElementById('themeIcon');
const body = document.body;
// 初始化主题
function initTheme() {
const savedTheme = localStorage.getItem('theme') || 'dark';
body.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
}
// 更新主题图标
function updateThemeIcon(theme) {
if (theme === 'dark') {
themeIcon.className = 'bi bi-sun';
themeToggle.title = '切换到浅色模式';
} else {
themeIcon.className = 'bi bi-moon';
themeToggle.title = '切换到深色模式';
}
}
// 切换主题
themeToggle.addEventListener('click', () => {
const currentTheme = body.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
body.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeIcon(newTheme);
});
// 全局变量和辅助函数
const getApiBase = () => document.getElementById('serverAddress').value;
const getAuthToken = () => document.getElementById('authToken').value;
// 历史记录管理
const STORAGE_KEY = 'cdj-web-manager-history';
function saveToHistory() {
const serverAddress = getApiBase();
const authToken = getAuthToken();
if (!serverAddress) return;
let history = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
// 移除重复项
history = history.filter(item => item.serverAddress !== serverAddress);
// 添加到开头
history.unshift({
serverAddress,
authToken,
lastUsed: Date.now()
});
// 只保留最近10条记录
history = history.slice(0, 10);
localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
updateHistoryDropdown();
}
function loadFromHistory() {
const history = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
if (history.length > 0) {
const latest = history[0];
document.getElementById('serverAddress').value = latest.serverAddress;
document.getElementById('authToken').value = latest.authToken || '';
}
updateHistoryDropdown();
}
function updateHistoryDropdown() {
const history = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
const dropdown = document.getElementById('historyDropdown');
if (history.length === 0) {
dropdown.innerHTML = '<li><span class="dropdown-item-text text-muted">暂无历史记录</span></li>';
return;
}
dropdown.innerHTML = history.map((item, index) => {
const date = new Date(item.lastUsed).toLocaleString();
return `
<li>
<a class="dropdown-item" href="#" onclick="loadHistoryItem(${index})" title="使用时间: ${date}">
<div class="d-flex justify-content-between align-items-center">
<span class="text-truncate" style="max-width: 200px;">${item.serverAddress}</span>
<button class="btn btn-outline-danger btn-sm ms-2" onclick="removeHistoryItem(${index}); event.stopPropagation();" title="删除">×</button>
</div>
</a>
</li>
`;
}).join('');
}
function loadHistoryItem(index) {
const history = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
if (history[index]) {
document.getElementById('serverAddress').value = history[index].serverAddress;
document.getElementById('authToken').value = history[index].authToken || '';
// 更新最后使用时间
history[index].lastUsed = Date.now();
localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
updateHistoryDropdown();
// 自动加载当前标签页数据
loadCurrentTabData();
}
}
function removeHistoryItem(index) {
let history = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
history.splice(index, 1);
localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
updateHistoryDropdown();
}
function clearAllHistory() {
if (confirm('确定要清除所有历史记录吗?')) {
localStorage.removeItem(STORAGE_KEY);
updateHistoryDropdown();
}
}
function loadCurrentTabData() {
const activeTab = document.querySelector('.tab-pane.active');
if (!activeTab || !getApiBase()) return;
const tabId = activeTab.id;
switch (tabId) {
case 'players-tab-pane':
loadPlayers();
break;
case 'onebot-tab-pane':
loadOneBotConfigs();
break;
case 'templates-tab-pane':
loadMessageTemplates();
break;
case 'server-tab-pane':
loadServerName();
break;
case 'token-tab-pane':
loadManageToken();
break;
}
}
// 清除当前标签页的提示信息并显示正确内容
function clearTabPlaceholder() {
const activeTab = document.querySelector('.tab-pane.active');
if (activeTab && activeTab.querySelector('.alert-info')) {
// 移除提示信息,恢复原始内容结构
if (activeTab.id === 'players-tab-pane') {
activeTab.innerHTML = `
<div class="card mt-3">
<div class="card-header">
<h5><i class="bi bi-people me-2"></i>玩家列表</h5>
<div>
<button class="btn btn-primary btn-sm" id="addPlayerBtn">
<i class="bi bi-plus-circle"></i> 添加玩家
</button>
<button class="btn btn-secondary btn-sm" id="refreshPlayersBtn">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>PUID</th>
<th>Token Platform</th>
<th>QQ ID</th>
<th>使用CDJ</th>
<th>OneBot 配置</th>
<th>操作</th>
</tr>
</thead>
<tbody id="playersTableBody">
<!-- 玩家数据将在这里动态生成 -->
</tbody>
</table>
</div>
</div>
</div>
`;
// 重新绑定事件监听器
rebindPlayerEvents();
}
}
}
// 重新绑定玩家管理相关的事件监听器
function rebindPlayerEvents() {
const addPlayerBtn = document.getElementById('addPlayerBtn');
const refreshPlayersBtn = document.getElementById('refreshPlayersBtn');
if (addPlayerBtn) {
addPlayerBtn.addEventListener('click', () => {
isEditingPlayer = false;
document.getElementById('playerForm').reset();
document.getElementById('playerModalTitle').textContent = '添加新玩家';
document.getElementById('player-puid').readOnly = false;
document.getElementById('player-tokenPlatform').readOnly = false;
playerModal.show();
});
}
if (refreshPlayersBtn) {
refreshPlayersBtn.addEventListener('click', loadPlayers);
}
}
async function apiFetch(endpoint, options = {}) {
const url = `${getApiBase()}${endpoint}`;
const headers = {
'Content-Type': 'application/json',
...options.headers,
};
const token = getAuthToken();
if (token) {
headers['Authorization'] = token;
}
try {
const response = await fetch(url, { ...options, headers });
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: '请求失败', message: response.statusText }));
throw new Error(errorData.message || errorData.error || '未知错误');
}
// DELETE 请求可能没有响应体
if (response.status === 204 || response.headers.get("content-length") === "0") {
return { success: true };
}
return await response.json();
} catch (error) {
alert(`API 操作失败: ${error.message}`);
throw error;
}
}
// 玩家管理
const playersTableBody = document.getElementById('playersTableBody');
const playerModal = new bootstrap.Modal(document.getElementById('playerModal'));
let isEditingPlayer = false;
async function loadPlayers() {
if (!getApiBase()) return;
try {
const players = await apiFetch('/players');
playersTableBody.innerHTML = '';
players.forEach(p => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${p.Puid}</td>
<td>${p.TokenPlatform}</td>
<td>${p.QqId}</td>
<td><span class="badge ${p.UseCDJ ? 'bg-success' : 'bg-secondary'}">${p.UseCDJ ? '是' : '否'}</span></td>
<td>${p.OneBotConfigNames.join(', ')}</td>
<td>
<button class="btn btn-warning btn-sm" onclick="editPlayer(this)"><i class="bi bi-pencil"></i> 编辑</button>
<button class="btn btn-danger btn-sm" onclick="deletePlayer('${p.Puid}', '${p.TokenPlatform}')"><i class="bi bi-trash"></i> 删除</button>
</td>
`;
row.dataset.player = JSON.stringify(p);
playersTableBody.appendChild(row);
});
} catch (error) {
console.error('加载玩家列表失败:', error);
}
}
document.getElementById('addPlayerBtn').addEventListener('click', () => {
isEditingPlayer = false;
document.getElementById('playerForm').reset();
document.getElementById('playerModalTitle').textContent = '添加新玩家';
document.getElementById('player-puid').readOnly = false;
document.getElementById('player-tokenPlatform').readOnly = false;
playerModal.show();
});
function editPlayer(btn) {
isEditingPlayer = true;
const playerData = JSON.parse(btn.closest('tr').dataset.player);
document.getElementById('playerModalTitle').textContent = '编辑玩家信息';
document.getElementById('player-original-puid').value = playerData.Puid;
document.getElementById('player-original-tokenPlatform').value = playerData.TokenPlatform;
document.getElementById('player-puid').value = playerData.Puid;
document.getElementById('player-tokenPlatform').value = playerData.TokenPlatform;
document.getElementById('player-puid').readOnly = true;
document.getElementById('player-tokenPlatform').readOnly = true;
document.getElementById('player-qqId').value = playerData.QqId;
document.getElementById('player-oneBotConfigNames').value = playerData.OneBotConfigNames.join(', ');
document.getElementById('player-announceRoomOnCreated').checked = playerData.AnnounceRoomOnCreated;
document.getElementById('player-announceGameStartEnd').checked = playerData.AnnounceGameStartEnd;
document.getElementById('player-useCDJ').checked = playerData.UseCDJ;
playerModal.show();
}
document.getElementById('savePlayerBtn').addEventListener('click', async () => {
const puid = document.getElementById('player-puid').value;
const tokenPlatform = document.getElementById('player-tokenPlatform').value;
const playerConfig = {
Puid: puid,
TokenPlatform: tokenPlatform,
QqId: parseInt(document.getElementById('player-qqId').value, 10),
AnnounceRoomOnCreated: document.getElementById('player-announceRoomOnCreated').checked,
AnnounceGameStartEnd: document.getElementById('player-announceGameStartEnd').checked,
UseCDJ: document.getElementById('player-useCDJ').checked,
OneBotConfigNames: document.getElementById('player-oneBotConfigNames').value.split(',').map(s => s.trim()).filter(Boolean),
};
try {
if (isEditingPlayer) {
const originalPuid = document.getElementById('player-original-puid').value;
const originalTokenPlatform = document.getElementById('player-original-tokenPlatform').value;
await apiFetch(`/players/${originalPuid}/${originalTokenPlatform}`, {
method: 'PUT',
body: JSON.stringify(playerConfig),
});
} else {
await apiFetch('/players', {
method: 'POST',
body: JSON.stringify(playerConfig),
});
}
playerModal.hide();
loadPlayers();
} catch (error) {
console.error('保存玩家失败:', error);
}
});
async function deletePlayer(puid, tokenPlatform) {
if (confirm(`确定要删除玩家 ${puid} (${tokenPlatform}) 吗?`)) {
try {
await apiFetch(`/players/${puid}/${tokenPlatform}`, { method: 'DELETE' });
loadPlayers();
} catch (error) {
console.error('删除玩家失败:', error);
}
}
}
document.getElementById('refreshPlayersBtn').addEventListener('click', loadPlayers);
// OneBot 配置管理
const oneBotConfigsTableBody = document.getElementById('oneBotConfigsTableBody');
const oneBotConfigModal = new bootstrap.Modal(document.getElementById('oneBotConfigModal'));
let isEditingOneBotConfig = false;
async function loadOneBotConfigs() {
if (!getApiBase()) return;
try {
const configs = await apiFetch('/onebot');
oneBotConfigsTableBody.innerHTML = '';
Object.entries(configs).forEach(([name, config]) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${name}</td>
<td><span class="badge ${config.Enabled ? 'bg-success' : 'bg-secondary'}">${config.Enabled ? '是' : '否'}</span></td>
<td>${config.Url}</td>
<td><span class="badge ${config.Type === 'private' ? 'bg-info' : 'bg-warning'}">${config.Type}</span></td>
<td>${config.TargetId}</td>
<td>
<button class="btn btn-warning btn-sm" onclick="editOneBotConfig(this)"><i class="bi bi-pencil"></i> 编辑</button>
<button class="btn btn-danger btn-sm" onclick="deleteOneBotConfig('${name}')"><i class="bi bi-trash"></i> 删除</button>
</td>
`;
row.dataset.name = name;
row.dataset.config = JSON.stringify(config);
oneBotConfigsTableBody.appendChild(row);
});
} catch (error) {
console.error('加载OneBot配置失败:', error);
}
}
document.getElementById('addOneBotConfigBtn').addEventListener('click', () => {
isEditingOneBotConfig = false;
document.getElementById('oneBotConfigForm').reset();
document.getElementById('oneBotConfigModalTitle').textContent = '添加新OneBot配置';
document.getElementById('onebot-name').readOnly = false;
oneBotConfigModal.show();
});
function editOneBotConfig(btn) {
isEditingOneBotConfig = true;
const name = btn.closest('tr').dataset.name;
const config = JSON.parse(btn.closest('tr').dataset.config);
document.getElementById('oneBotConfigModalTitle').textContent = '编辑OneBot配置';
document.getElementById('onebot-original-name').value = name;
document.getElementById('onebot-name').value = name;
document.getElementById('onebot-name').readOnly = true;
document.getElementById('onebot-url').value = config.Url;
document.getElementById('onebot-token').value = config.Token;
document.getElementById('onebot-type').value = config.Type;
document.getElementById('onebot-targetId').value = config.TargetId;
document.getElementById('onebot-enabled').checked = config.Enabled;
oneBotConfigModal.show();
}
document.getElementById('saveOneBotConfigBtn').addEventListener('click', async () => {
const name = document.getElementById('onebot-name').value;
const config = {
Enabled: document.getElementById('onebot-enabled').checked,
Url: document.getElementById('onebot-url').value,
Token: document.getElementById('onebot-token').value,
Type: document.getElementById('onebot-type').value,
TargetId: parseInt(document.getElementById('onebot-targetId').value, 10),
};
try {
const originalName = document.getElementById('onebot-original-name').value;
const endpoint = isEditingOneBotConfig ? `/onebot/${originalName}` : `/onebot/${name}`;
const method = isEditingOneBotConfig ? 'PUT' : 'POST';
if (isEditingOneBotConfig) {
await apiFetch(`/onebot/${originalName}`, { method: 'PUT', body: JSON.stringify(config) });
} else {
await apiFetch(`/onebot/${name}`, { method: 'POST', body: JSON.stringify(config) });
}
oneBotConfigModal.hide();
loadOneBotConfigs();
} catch (error) {
console.error('保存OneBot配置失败:', error);
}
});
async function deleteOneBotConfig(name) {
if (confirm(`确定要删除配置 ${name} 吗?`)) {
try {
await apiFetch(`/onebot/${name}`, { method: 'DELETE' });
loadOneBotConfigs();
} catch (error) {
console.error('删除OneBot配置失败:', error);
}
}
}
document.getElementById('refreshOneBotConfigsBtn').addEventListener('click', loadOneBotConfigs);
// 消息模板管理
async function loadMessageTemplates() {
if (!getApiBase()) return;
try {
const templates = await apiFetch('/messages');
document.getElementById('templateRoomCreated').value = templates.RoomCreated;
document.getElementById('templateGameStarted').value = templates.GameStarted;
document.getElementById('templateGameEnded').value = templates.GameEnded;
document.getElementById('templateHostChanged').value = templates.HostChanged;
document.getElementById('templateCdjAvailable').value = templates.CdjAvailableNotice;
} catch (error) {
console.error('加载消息模板失败:', error);
}
}
document.getElementById('saveMessageTemplatesBtn').addEventListener('click', async () => {
const templates = {
RoomCreated: document.getElementById('templateRoomCreated').value,
GameStarted: document.getElementById('templateGameStarted').value,
GameEnded: document.getElementById('templateGameEnded').value,
HostChanged: document.getElementById('templateHostChanged').value,
CdjAvailableNotice: document.getElementById('templateCdjAvailable').value,
};
try {
await apiFetch('/messages', {
method: 'PUT',
body: JSON.stringify(templates),
});
alert('消息模板更新成功!');
loadMessageTemplates();
} catch (error) {
console.error('更新消息模板失败:', error);
}
});
document.getElementById('refreshMessageTemplatesBtn').addEventListener('click', loadMessageTemplates);
// 服务器名称管理
async function loadServerName() {
if (!getApiBase()) return;
try {
const data = await apiFetch('/server-name');
document.getElementById('serverNameInput').value = data.serverName;
} catch (error) {
console.error('加载服务器名称失败:', error);
}
}
document.getElementById('updateServerNameBtn').addEventListener('click', async () => {
const serverName = document.getElementById('serverNameInput').value;
try {
await apiFetch('/server-name', {
method: 'PUT',
body: JSON.stringify({ serverName: serverName }), // 修改:发送对象而不是纯字符串
headers: { 'Content-Type': 'application/json' }
});
alert('服务器名称更新成功!');
loadServerName();
} catch (error) {
console.error('更新服务器名称失败:', error);
}
});
document.getElementById('refreshServerNameBtn').addEventListener('click', loadServerName);
// Token 管理
async function loadManageToken() {
if (!getApiBase()) return;
try {
const data = await apiFetch('/token');
const currentToken = data.manageToken || '';
document.getElementById('currentTokenDisplay').value = currentToken;
document.getElementById('newTokenInput').value = '';
} catch (error) {
console.error('加载管理Token失败:', error);
}
}
// 生成随机 Token
function generateRandomToken() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < 16; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
document.getElementById('generateTokenBtn').addEventListener('click', () => {
const newToken = generateRandomToken();
document.getElementById('newTokenInput').value = newToken;
});
// 切换当前 Token 显示/隐藏
document.getElementById('toggleCurrentTokenBtn').addEventListener('click', () => {
const tokenDisplay = document.getElementById('currentTokenDisplay');
const toggleBtn = document.getElementById('toggleCurrentTokenBtn');
if (tokenDisplay.type === 'password') {
tokenDisplay.type = 'text';
toggleBtn.innerHTML = '<i class="bi bi-eye-slash"></i>';
} else {
tokenDisplay.type = 'password';
toggleBtn.innerHTML = '<i class="bi bi-eye"></i>';
}
});
document.getElementById('updateTokenBtn').addEventListener('click', async () => {
const newToken = document.getElementById('newTokenInput').value.trim();
if (newToken === '') {
if (!confirm('您即将清空管理 Token,这将禁用 Token 验证,任何人都可以访问管理接口。确定要继续吗?')) {
return;
}
}
try {
const result = await apiFetch('/token', {
method: 'PUT',
body: JSON.stringify({ manageToken: newToken }),
headers: { 'Content-Type': 'application/json' }
});
if (result.success) {
alert('管理 Token 更新成功!\n\n提示:新的 Token 已自动保存到配置文件中。如果您设置了新的 Token,请记得在上方的 "认证 Token" 字段中输入新的 Token 以继续管理。');
// 如果设置了新 Token,自动更新认证字段
if (newToken !== '') {
document.getElementById('authToken').value = newToken;
saveToHistory(); // 保存到历史记录
}
loadManageToken(); // 刷新显示
} else {
alert('更新失败:' + (result.message || '未知错误'));
}
} catch (error) {
console.error('更新管理Token失败:', error);
}
});
document.getElementById('refreshTokenBtn').addEventListener('click', loadManageToken);
// 初始化加载
document.addEventListener('DOMContentLoaded', () => {
// 初始化主题
initTheme();
// 加载历史记录
loadFromHistory();
// 监听服务器地址和token变化,自动保存
const serverAddressInput = document.getElementById('serverAddress');
const authTokenInput = document.getElementById('authToken');
function handleInputChange() {
if (getApiBase()) {
clearTabPlaceholder(); // 清除提示信息
saveToHistory();
loadCurrentTabData();
}
}
serverAddressInput.addEventListener('blur', handleInputChange);
authTokenInput.addEventListener('blur', handleInputChange);
// 当切换标签页时,加载对应的数据
const tabElms = document.querySelectorAll('button[data-bs-toggle="tab"]');
tabElms.forEach(tabElm => {
tabElm.addEventListener('shown.bs.tab', (event) => {
const targetId = event.target.getAttribute('data-bs-target');
switch (targetId) {
case '#players-tab-pane':
loadPlayers();
break;
case '#onebot-tab-pane':
loadOneBotConfigs();
break;
case '#templates-tab-pane':
loadMessageTemplates();
break;
case '#server-tab-pane':
loadServerName();
break;
case '#token-tab-pane':
loadManageToken();
break;
}
});
});
// 默认加载第一个标签页的数据
if (getApiBase()) {
loadPlayers();
} else {
// 提示用户输入服务器地址
const firstTabPane = document.querySelector('.tab-pane.active');
if (firstTabPane) {
firstTabPane.innerHTML = '<div class="alert alert-info mt-3">请输入API服务器地址以开始。</div>';
}
}
});
</script>
</body>
</html>
3.自维护安装器批处理程序(工具箱)
(((本人懂得不多,大部分AI生成
@echo off
chcp 65001 > nul
title 帆船AmongUs服务器安装器
:: 设置颜色
for /f "delims=#" %%a in ('"prompt #$E# & for %%a in (1) do rem"') do set "ESC=%%a"
set "GREEN=%ESC%[92m"
set "YELLOW=%ESC%[93m"
set "CYAN=%ESC%[96m"
set "RED=%ESC%[91m"
set "BLUE=%ESC%[94m"
set "MAGENTA=%ESC%[95m"
set "ORANGE=%ESC%[38;5;214m"
set "PURPLE=%ESC%[38;5;129m"
set "GOLD=%ESC%[38;5;220m"
set "RESET=%ESC%[0m"
:: 隐藏光标
echo %ESC%[?25l
:: 检查管理员权限
call :check_admin_privileges
:: 确保C盘文件夹存在
if not exist "C:\fanchuanauserver" (
mkdir "C:\fanchuanauserver" >nul 2>&1
)
:: 显示启动画面
call :startup_screen
:: 初始化界面
call :loading_screen_with_banner "正在初始化系统" 5
:: 检查依赖项界面
call :loading_screen_with_banner "正在检查依赖项" 5
:: 检查系统版本界面
call :loading_screen_with_banner "正在检查系统版本" 5
:: 检查旧配置文件
if exist "C:\fanchuanauserver" (
call :check_old_config
)
:: 显示主菜单
:main_menu
call :display_header "主菜单"
echo.
echo %GREEN%[U]%RESET% 检查当前工具箱版本是否为最新,避免使用到旧版脚本或旧版的帆船服导致影响游戏!
echo.
echo %BLUE%[1]%RESET% 安装帆船AmongUs服务器
echo.
echo %BLUE%[2]%RESET% 修复旧版游戏
echo.
echo %BLUE%[3]%RESET% 联系服主
echo.
echo %BLUE%[4]%RESET% 跳转官网
echo.
echo %BLUE%[5]%RESET% 跳转到在线安装源服务器(欢迎其他安装器作者使用)
echo.
echo %BLUE%[6]%RESET% 查看脚本源代码
echo.
echo %BLUE%[7]%RESET% 跳转到帆船服疑难解答
echo.
echo %BLUE%[8]%RESET% 当前版本更新日志
echo.
echo %BLUE%[9]%RESET% 关于
echo.
echo %RED%[0]%RESET% 退出
echo.
echo.
set /p "choice= 请选择 [U,0-9]: "
if /i "%choice%"=="U" (
goto :check_update
) else if "%choice%"=="1" (
goto :install_menu
) else if "%choice%"=="2" (
goto :repair_old_version
) else if "%choice%"=="3" (
goto :contact_owner
) else if "%choice%"=="4" (
goto :open_website
) else if "%choice%"=="5" (
goto :open_install_source
) else if "%choice%"=="6" (
goto :view_source_code
) else if "%choice%"=="7" (
goto :troubleshoot
) else if "%choice%"=="8" (
goto :changelog
) else if "%choice%"=="9" (
goto :about
) else if "%choice%"=="0" (
goto :exit_program
) else (
echo.
echo %RED%无效选择,请重新输入%RESET%
timeout /t 2 > nul
goto :main_menu
)
:: 检查更新
:check_update
call :display_header "检查更新"
echo.
echo %CYAN%正在唤起浏览器检查最新版本...%RESET%
echo.
start "" "http://update.20251101.auazq.fanchuanovo.cn"
echo %GREEN%已成功唤起浏览器检查更新!%RESET%
echo.
echo %YELLOW%请检查默认浏览器是否打开了新页面%RESET%
echo.
echo %CYAN%按任意键返回主菜单...%RESET%
echo.
pause > nul
goto :main_menu
:: 安装方式选择菜单
:install_menu
call :display_header "请选择安装方式"
echo.
echo %BLUE%[1] 在线安装%RESET% - 从服务器下载最新配置文件
echo.
echo %BLUE%[2] 离线安装%RESET% - 使用内置配置文件
echo.
echo %BLUE%[0] 返回主菜单%RESET%
echo.
echo.
set /p "choice= 请选择 [0-2]: "
if "%choice%"=="1" (
goto :online_install
) else if "%choice%"=="2" (
goto :offline_install
) else if "%choice%"=="0" (
goto :main_menu
) else (
echo.
echo %RED%无效选择,请重新输入%RESET%
timeout /t 2 > nul
goto :install_menu
)
:: 修复旧版游戏功能
:repair_old_version
call :display_header "修复旧版游戏"
echo.
echo %CYAN%代码来源:沫夏悠轩;搬运到本安装器:帆船%RESET%
echo.
echo %YELLOW%用于解决打开新版游戏后旧版游戏黑屏的问题%RESET%
echo.
echo %CYAN%准备下载旧版本游戏设置%RESET%
echo.
echo.
echo %YELLOW%请按任何按键确认执行,否则请关闭脚本。%RESET%
echo.
pause > nul
cls
call :display_header "修复旧版游戏"
echo.
echo %CYAN%正在下载旧版本游戏设置...%RESET%
echo.
curl "http://api.mxyx.club/download/among-us/OtherFiles/settings.amogus" -o "%AppData%\..\LocalLow\Innersloth\Among Us\settings.amogus"
IF %ERRORLEVEL% NEQ 0 (
echo %RED%下载失败%RESET%
pause > nul
goto :main_menu
)
echo.
echo %GREEN%成功修复旧版游戏设置!%RESET%
echo.
:: 创建菜单文件
call :create_menu_file_with_prompt
goto :main_menu
:: 联系服主
:contact_owner
call :loading_screen "正在联系服主" 2
start "" "https://qm.qq.com/q/nm4yfCdaWQ"
echo %ESC%[?25h
exit /b 0
:: 跳转官网
:open_website
call :loading_screen "正在跳转官网" 2
start "" "https://au.fanchuanovo.cn"
echo %ESC%[?25h
exit /b 0
:: 跳转到在线安装源服务器
:open_install_source
call :loading_screen "正在跳转到在线安装源服务器" 2
start "" "https://au.fanchuanovo.cn/wp-content/uploads/2025/10/regionInfo.json"
echo %ESC%[?25h
exit /b 0
:: 查看脚本源代码
:view_source_code
call :loading_screen "正在跳转到脚本源代码" 2
start "" "https://au.fanchuanovo.cn/?p=234"
echo %ESC%[?25h
exit /b 0
:: 帆船服疑难解答
:troubleshoot
call :loading_screen "正在跳转到疑难解答" 2
start "" "https://au.fanchuanovo.cn/?p=177"
echo %ESC%[?25h
exit /b 0
:: 当前版本更新日志
:changelog
call :display_header "当前版本更新日志"
echo.
echo %CYAN%v202511011308(2.4)更新日志:%RESET%
echo.
echo %YELLOW%1. 完善加载圈%RESET%
echo.
echo %YELLOW%2. 添加更新日志,检查更新,跳转到疑难解答等功能%RESET%
echo.
echo %YELLOW%3. 修复了一些已知问题,新增了一堆未知问题( (欢迎反馈)%RESET%
echo.
echo %YELLOW%4. 把主菜单退出选项的序号改为0%RESET%
echo.
echo %CYAN%按任意键返回主菜单...%RESET%
echo.
pause > nul
goto :main_menu
:: 关于页面
:about
call :display_header "关于"
echo.
echo %CYAN%制作:帆船%RESET%
echo.
echo %CYAN%当前版本:v2.4多功能官方发行版%RESET%
echo.
echo %CYAN%当前版本:当前版本内测时间:2025年11月1日 12:30%RESET%
echo.
echo %CYAN%当前版本预计发布时间:2025年11月1日 23:59前%RESET%
echo.
echo %CYAN%©帆船AmongUs服务器 版权所有%RESET%
echo.
echo %YELLOW%按任意键返回主菜单...%RESET%
echo.
pause > nul
goto :main_menu
:: 在线安装
:online_install
call :display_header "在线安装中"
echo.
:: 创建C盘文件夹
call :spinner_with_text "正在创建文件夹" 1
if not exist "C:\fanchuanauserver" (
mkdir "C:\fanchuanauserver" >nul 2>&1
if errorlevel 1 (
echo %RED%无法创建文件夹,请以管理员身份运行%RESET%
goto :error
)
)
:: 下载JSON文件
call :spinner_with_text "正在连接到安装源服务器下载配置文件..." 2
powershell -Command "Invoke-WebRequest -Uri 'https://au.fanchuanovo.cn/wp-content/uploads/2025/10/regionInfo.json' -OutFile 'C:\fanchuanauserver\regionInfo.json'" >nul 2>&1
if errorlevel 1 (
echo %RED%下载失败,切换到离线安装模式...%RESET%
timeout /t 2 >nul
goto :offline_install
)
:: 复制文件到游戏目录
call :copy_to_game
:: 创建菜单文件
call :create_menu_file_with_prompt
:: 询问是否设置只读模式
call :set_readonly_prompt
:: 完成界面
goto :install_complete
:: 离线安装
:offline_install
call :display_header "离线安装中"
echo.
:: 创建C盘文件夹并保存离线版本
call :spinner_with_text "正在创建文件夹" 1
if not exist "C:\fanchuanauserver" (
mkdir "C:\fanchuanauserver" >nul 2>&1
)
:: 写入JSON内容到C盘
call :spinner_with_text "正在调用内置配置文件" 2
(
echo {
echo "CurrentRegionIdx": 5,
echo "Regions": [
echo {
echo "$type":"StaticHttpRegionInfo, Assembly-CSharp",
echo "Name":"<color=#ff7518>\u5e06\u8239\u670d</color><color=#ffff00>[\u5e7f\u5dde]</color>",
echo "PingServer":"gz.fcaugame.cn",
echo "Servers":[
echo {
echo "Name":"http-1",
echo "Ip":"gz.fcaugame.cn",
echo "Port":443,
echo "UseDtls":false,
echo "Players":0,
echo "ConnectionFailures":0
echo }
echo ],
echo "TargetServer":null,
echo "TranslateName":1003
echo }
echo ]
echo }
) > "C:\fanchuanauserver\regionInfo.json"
:: 复制文件到游戏目录
call :copy_to_game
:: 创建菜单文件
call :create_menu_file_with_prompt
:: 询问是否设置只读模式
call :set_readonly_prompt
:: 完成界面
:install_complete
goto :main_menu
:: 复制文件到游戏目录
:copy_to_game
:: 获取APPDATA路径
for /f "tokens=*" %%i in ('echo %APPDATA%') do set APPDATA_PATH=%%i
:: 构建目标文件路径
set TARGET_PATH=%APPDATA_PATH%\..\LocalLow\Innersloth\Among Us\regionInfo.json
:: 创建目录(如果不存在)
set TARGET_DIR=%APPDATA_PATH%\..\LocalLow\Innersloth\Among Us
call :spinner_with_text "正在创建游戏目录" 1
if not exist "%TARGET_DIR%" (
mkdir "%TARGET_DIR%" >nul 2>&1
if errorlevel 1 (
echo %RED%无法创建游戏目录,请以管理员身份运行%RESET%
goto :error
)
)
:: 如果目标文件存在且为只读,先去除只读属性
if exist "%TARGET_PATH%" (
attrib -R "%TARGET_PATH%" >nul 2>&1
)
:: 复制文件
call :spinner_with_text "正在复制文件到游戏目录" 2
copy "C:\fanchuanauserver\regionInfo.json" "%TARGET_PATH%" >nul 2>&1
if errorlevel 1 (
echo %RED%复制文件失败%RESET%
goto :error
)
echo %GREEN%文件复制成功!%RESET%
timeout /t 2 > nul
goto :eof
:: 创建菜单文件(带提示和选项)
:create_menu_file_with_prompt
call :spinner_with_text "正在创建菜单文件" 1
:: 如果文件已存在,则删除
if exist "C:\fanchuanauserver\caidan.txt" (
del "C:\fanchuanauserver\caidan.txt" >nul 2>&1
)
(
echo 11100110100000011010110111100101100101101001110011100100101111011010000011100101100011111001000111100111100011101011000011100100101110101000011011100101101110001000011011101000100010001011100111100110100111001000110111100110101011011010001111100111100010011000100011100101101011101000100111101000101000111000010111100101100110011010100011100111100110101000010011101001100110001011001011100100101111001010101011100101101111011010100111101000100110111000101111101111101111001000110011100100101111011000011011100110100110001010111111100110101100101010000111100110100111001000100111100101101001011001011011100101100010101011000111101111101111001001101011101111101111001000100111101111101111001000110011100101101111011001001111100101100010011000110111100111100010011000100011100110100111001010110000110010001011100011010011100101101001001001101011100101100010101001111111101000100000111011110111100101101011101001100011100110100101101011100111100101100011111001000111101000101000011000110011100111100010011000100000001010
) > "C:\fanchuanauserver\caidan.txt"
cls
call :display_header "一个二进制编码小彩蛋"
echo.
echo %CYAN%我们放置了一枚小菜单在您的电脑内,不是病毒,小于5kb,不影响电脑运行,期待您的寻找!%RESET%
echo.
echo %BLUE%[1]%RESET% 我不需要,请帮我删除
echo.
echo %BLUE%[2]%RESET% 我不想寻找彩蛋,我要查看
echo.
echo %BLUE%[3]%RESET% 返回主菜单
echo.
echo.
set /p "menu_choice= 请选择 [1-3]: "
if "%menu_choice%"=="1" (
echo.
echo %CYAN%正在删除文件夹...%RESET%
rd /s /q "C:\fanchuanauserver" >nul 2>&1
echo %GREEN%文件夹已删除!%RESET%
timeout /t 2 > nul
) else if "%menu_choice%"=="2" (
echo.
echo %CYAN%正在打开文件夹...%RESET%
start "" "C:\fanchuanauserver"
echo %ESC%[?25h
exit /b 0
) else if "%menu_choice%"=="3" (
goto :eof
) else (
echo.
echo %RED%无效选择,将返回主菜单%RESET%
timeout /t 2 > nul
)
goto :eof
:: 设置只读模式提示
:set_readonly_prompt
call :display_header "文件保护设置"
echo.
echo %CYAN%是否切换为只读模式?%RESET%
echo.
echo %YELLOW%只读模式能有效防止文件被其他安装器和恶意程序篡改%RESET%
echo.
echo %BLUE%[1]%RESET% 是,设置为只读模式
echo.
echo %BLUE%[2]%RESET% 否,返回主菜单
echo.
echo.
set /p "readonly_choice= 请选择 [1-2]: "
if "%readonly_choice%"=="1" (
call :set_file_readonly
echo.
echo %GREEN%文件已设置为只读模式!%RESET%
timeout /t 2 > nul
) else if "%readonly_choice%"=="2" (
goto :eof
) else (
echo.
echo %RED%无效选择,将返回主菜单%RESET%
timeout /t 2 > nul
)
goto :eof
:: 设置文件只读
:set_file_readonly
:: 获取APPDATA路径
for /f "tokens=*" %%i in ('echo %APPDATA%') do set APPDATA_PATH=%%i
:: 构建目标文件路径
set TARGET_PATH=%APPDATA_PATH%\..\LocalLow\Innersloth\Among Us\regionInfo.json
:: 设置文件为只读
attrib +R "%TARGET_PATH%" >nul 2>&1
goto :eof
:: 创建菜单文件(不带提示)
:create_menu_file
:: 如果文件已存在,则删除
if exist "C:\fanchuanauserver\caidan.txt" (
del "C:\fanchuanauserver\caidan.txt" >nul 2>&1
)
(
echo 11100110100000011010110111100101100101101001110011100100101111011010000011100101100011111001000111100111100011101011000011100100101110101000011011100101101110001000011011101000100010001011100111100110100111001000110111100110101011011010001111100111100010011000100011100101101011101000100111101000101000111000010111100101100110011010100011100111100110101000010011101001100110001011001011100100101111001010101011100101101111011010100111101000100110111000101111101111101111001000110011100100101111011000011011100110100110001010111111100110101100101010000111100110100111001000100111100101101001011001011011100101100010101011000111101111101111001001101011101111101111001000100111101111101111001000110011100101101111011001001111100101100010011000110111100111100010011000100011100110100111001010110000110010001011100011010011100101101001001001101011100101100010101001111111101000100000111011110111100101101011101001100011100110100101101011100111100101100011111001000111101000101000011000110011100111100010011000100000001010
goto :eof
:: 检查管理员权限
:check_admin_privileges
net session >nul 2>&1
if %errorlevel% equ 0 (
goto :eof
)
call :display_header "权限警告"
echo.
echo %RED%检测到当前脚本非管理员权限运行,少数电脑可能无法完成安装%RESET%
echo.
echo %BLUE%[1]%RESET% 重新以管理员权限启动
echo.
echo %BLUE%[2]%RESET% 我又不是少数,继续使用!
echo.
echo.
set /p "admin_choice= 请选择 [1-2]: "
if "%admin_choice%"=="1" (
echo.
echo %CYAN%正在以管理员权限重新启动...%RESET%
echo %ESC%[?25h
PowerShell -Command "Start-Process '%~f0' -Verb RunAs" >nul 2>&1
exit
) else if "%admin_choice%"=="2" (
goto :eof
) else (
echo.
echo %RED%无效选择,将继续执行程序%RESET%
timeout /t 2 > nul
)
goto :eof
:: 检查旧配置文件
:check_old_config
call :display_header "警告"
echo.
echo %RED%检测到您的电脑内可能安装了过老的帆船服配置文件,无法进入游戏,请尽快使用此官方脚本的最新版本进行重新安装避免耽误游戏!%RESET%
echo.
echo %YELLOW%按任意键继续...%RESET%
echo.
pause > nul
goto :eof
:: 启动画面
:startup_screen
cls
echo.
echo.
echo %MAGENTA%███████╗ █████╗ ███╗ ██╗ ██████╗██╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗%RESET%
echo %CYAN%██╔════╝██╔══██╗████╗ ██║██╔════╝██║ ██║██║ ██║██╔══██╗████╗ ██║%RESET%
echo %GREEN%█████╗ ███████║██╔██╗ ██║██║ ███████║██║ ██║███████║██╔██╗ ██║%RESET%
echo %YELLOW%██╔══╝ ██╔══██║██║╚██╗██║██║ ██╔══██║██║ ██║██╔══██║██║╚██╗██║%RESET%
echo %RED%██║ ██║ ██║██║ ╚████║╚██████╗██║ ██║╚██████╔╝██║ ██║██║ ╚████║%RESET%
echo %ORANGE%╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝%RESET%
echo.
echo %CYAN%正在初始化程序...%RESET%
echo.
call :spinner 78
goto :eof
:: 带横幅的加载屏幕
:loading_screen_with_banner
setlocal
set "text=%~1"
set /a "duration=%~2"
cls
echo.
echo.
echo %MAGENTA%███████╗ █████╗ ███╗ ██╗ ██████╗██╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗%RESET%
echo %CYAN%██╔════╝██╔══██╗████╗ ██║██╔════╝██║ ██║██║ ██║██╔══██╗████╗ ██║%RESET%
echo %GREEN%█████╗ ███████║██╔██╗ ██║██║ ███████║██║ ██║███████║██╔██╗ ██║%RESET%
echo %YELLOW%██╔══╝ ██╔══██║██║╚██╗██║██║ ██╔══██║██║ ██║██╔══██║██║╚██╗██║%RESET%
echo %RED%██║ ██║ ██║██║ ╚████║╚██████╗██║ ██║╚██████╔╝██║ ██║██║ ╚████║%RESET%
echo %ORANGE%╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝%RESET%
echo.
echo %CYAN%%text%...%RESET%
echo.
call :spinner %duration%
echo.
endlocal
goto :eof
:: 显示标题头
:display_header
setlocal
set "step_name=%~1"
cls
echo.
echo %MAGENTA%=============================================%RESET%
echo %CYAN% 丨%GREEN%帆船%YELLOW%AmongUs%RED%服务器%BLUE%工具箱%PURPLE%丨%RESET%
echo %MAGENTA%=============================================%RESET%
echo %GOLD% %step_name%%RESET%
echo.
endlocal
goto :eof
:: 加载屏幕(带旋转动画)
:loading_screen
setlocal
set "text=%~1"
set /a "duration=%~2"
call :display_header "%text%"
echo.
call :spinner_with_text "%text%" %duration%
echo.
endlocal
goto :eof
:: 带文字的旋转动画函数 - 修复版本
:spinner_with_text
setlocal enabledelayedexpansion
set "text=%~1"
set /a "duration=%~2"
set "frames=⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
set "frame_count=10"
set /a "total_loops=duration*5"
for /l %%i in (1,1,%total_loops%) do (
set /a "frame_index=(%%i-1) %% frame_count"
for /f "tokens=1,2" %%a in ("!frame_index!") do (
<nul set /p "=%CYAN%!frames:~%%a,1!%RESET% %YELLOW%%text%...%RESET%"
)
ping -n 1 -w 200 127.0.0.1 > nul
<nul set /p "=%ESC%[2K%ESC%[0G"
)
echo %GREEN%✓%RESET% %YELLOW%%text%完成%RESET%
endlocal
goto :eof
:: 旋转动画函数 - 修复版本
:spinner
setlocal enabledelayedexpansion
set /a "duration=%~1"
set "frames=⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
set "frame_count=10"
set /a "total_loops=duration*5"
for /l %%i in (1,1,%total_loops%) do (
set /a "frame_index=(%%i-1) %% frame_count"
for /f "tokens=1,2" %%a in ("!frame_index!") do (
<nul set /p "=%YELLOW%!frames:~%%a,1!%RESET%"
)
ping -n 1 -w 200 127.0.0.1 > nul
<nul set /p "=%ESC%[1D"
)
echo.
endlocal
goto :eof
:: 退出程序
:exit_program
call :display_header "感谢使用"
echo.
echo %CYAN%程序即将退出...%RESET%
echo.
call :spinner 2
echo %ESC%[?25h
exit /b 0
:: 错误处理
:error
echo.
echo %RED%安装失败!%RESET%
echo.
echo %YELLOW%可能的解决方案:%RESET%
echo %CYAN%1. 以管理员身份运行此程序%RESET%
echo %CYAN%2. 关闭Among Us游戏和其他可能使用此文件的程序%RESET%
echo %CYAN%3. 检查网络连接(在线安装)%RESET%
echo.
echo %CYAN%按任意键返回主菜单...%RESET%
echo %ESC%[?25h
pause > nul
goto :main_menu
0 条评论