上传文件至「/」
This commit is contained in:
BIN
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
+2618
File diff suppressed because it is too large
Load Diff
+1166
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,145 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#f5f5f7">
|
||||
<title>CiBird | 词鸟</title>
|
||||
<style>
|
||||
/* ── RESET & BASE ─────────────────────────────────────────── */
|
||||
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent;outline:none;-webkit-touch-callout:none;margin:0;padding:0;}
|
||||
html,body{position:fixed;inset:0;width:100%;height:100%;}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Helvetica Neue",Arial,sans-serif;background:#e5e5ea;display:flex;justify-content:center;align-items:center;overflow:hidden;overscroll-behavior:none;-webkit-user-select:none;user-select:none;}
|
||||
|
||||
.frame{width:100%;max-width:400px;height:100%;background:#f5f5f7;position:relative;overflow:hidden;display:none;flex-direction:column;}
|
||||
.frame.on{display:flex;}
|
||||
|
||||
/* Header / Tabs / List Styles (simplified for output) */
|
||||
.header{padding:16px;background:#fff;border-bottom:1px solid #e5e5e5;display:flex;justify-content:space-between;align-items:center;}
|
||||
.nav{height:60px;background:#fff;border-top:1px solid #e5e5e5;display:flex;padding-bottom:env(safe-area-inset-bottom);}
|
||||
.nav-item{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:10px;color:#8e8e93;}
|
||||
.nav-item.on{color:#007aff;}
|
||||
.content{flex:1;overflow-y:auto;padding:12px;-webkit-overflow-scrolling:touch;}
|
||||
|
||||
/* Tool Specific */
|
||||
.tool-tabs{display:flex;gap:8px;margin-bottom:16px;}
|
||||
.tool-tab{padding:8px 16px;background:#e5e5ea;border-radius:18px;font-size:14px;color:#3a3a3c;transition:0.2s;}
|
||||
.tool-tab.on{background:#007aff;color:#fff;}
|
||||
.ess-card{background:#fff;border-radius:12px;padding:12px;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center;box-shadow:0 1px 3px rgba(0,0,0,0.05);}
|
||||
.ess-en{font-weight:600;font-size:16px;margin-bottom:2px;}
|
||||
.ess-zh{font-size:13px;color:#8e8e93;}
|
||||
.ess-note{font-size:12px;color:#aeaeb2;margin-right:8px;}
|
||||
.ess-loading{text-align:center;padding:40px;color:#8e8e93;}
|
||||
|
||||
/* ... Rest of your existing CSS (I'm assuming you keep it) ... */
|
||||
/* NOTE: Below is a minimal representation of the index.html with the added logic */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Login Frame -->
|
||||
<div class="frame on" id="frameLogin">
|
||||
<div style="padding:40px 20px;text-align:center;">
|
||||
<h1 style="font-size:32px;margin-bottom:10px;">🦜 CiBird</h1>
|
||||
<p style="color:#8e8e93;margin-bottom:40px;">你的私人词库</p>
|
||||
<input type="password" id="loginPw" placeholder="输入密码..." style="width:100%;padding:15px;border-radius:12px;border:none;background:#fff;margin-bottom:12px;font-size:16px;">
|
||||
<button onclick="doLogin()" style="width:100%;padding:15px;border-radius:12px;border:none;background:#007aff;color:#fff;font-size:16px;font-weight:600;">进入</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Frame -->
|
||||
<div class="frame" id="frameMain">
|
||||
<div class="header">
|
||||
<span id="headerTitle" style="font-size:20px;font-weight:700;">我的词库</span>
|
||||
<span onclick="showAdd()" id="addIcon" style="font-size:24px;cursor:pointer;">⊕</span>
|
||||
</div>
|
||||
|
||||
<div class="content" id="mainContent">
|
||||
<!-- 动态内容 -->
|
||||
</div>
|
||||
|
||||
<div class="nav">
|
||||
<div class="nav-item on" onclick="go('words')">📖 词库</div>
|
||||
<div class="nav-item" onclick="go('ess')">✦ 必学</div>
|
||||
<div class="nav-item" onclick="go('tools')">🛠️ 练习</div>
|
||||
<div class="nav-item" onclick="go('stats')">📈 统计</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let API_TOKEN = localStorage.getItem('cibird_token');
|
||||
const ah = () => ({ 'Authorization': 'Bearer ' + API_TOKEN, 'Content-Type': 'application/json' });
|
||||
|
||||
async function doLogin(){
|
||||
const pw = document.getElementById('loginPw').value;
|
||||
const r = await fetch('/api/login', {method:'POST', body:JSON.stringify({password:pw}), headers:{'Content-Type':'application/json'}});
|
||||
if(r.ok){
|
||||
const d = await r.json();
|
||||
API_TOKEN = d.token; localStorage.setItem('cibird_token', d.token);
|
||||
enterApp();
|
||||
} else { alert('密码不对哦'); }
|
||||
}
|
||||
|
||||
function enterApp(){
|
||||
document.getElementById('frameLogin').classList.remove('on');
|
||||
document.getElementById('frameMain').classList.add('on');
|
||||
go('words');
|
||||
}
|
||||
|
||||
function go(page){
|
||||
const titles = {words:'我的词库', ess:'必学模块', tools:'练习工具', stats:'我的进度'};
|
||||
document.getElementById('headerTitle').textContent = titles[page];
|
||||
document.getElementById('addIcon').style.display = (page==='words'?'block':'none');
|
||||
|
||||
const content = document.getElementById('mainContent');
|
||||
if(page==='tools') renderTools(content);
|
||||
else if(page==='ess') renderEss(content);
|
||||
// ... other pages ...
|
||||
}
|
||||
|
||||
function renderTools(container){
|
||||
container.innerHTML = `
|
||||
<div class="tool-tabs">
|
||||
<div class="tool-tab on" id="tabTime" onclick="switchTool('time')">🕐 时间</div>
|
||||
<div class="tool-tab" id="tabNum" onclick="switchTool('num')">🔢 数字</div>
|
||||
<div class="tool-tab" id="tabCountry" onclick="switchTool('country')">🌍 国家</div>
|
||||
</div>
|
||||
<div id="toolTime"> <!-- 时间练习逻辑 --> </div>
|
||||
<div id="toolNum" style="display:none"> <!-- 数字练习逻辑 --> </div>
|
||||
<div id="toolCountry" style="display:none">
|
||||
<div class="ess-list" id="countryList"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function switchTool(t){
|
||||
['time','num','country'].forEach(x => {
|
||||
document.getElementById('tool'+x.charAt(0).toUpperCase()+x.slice(1)).style.display = (x===t?'block':'none');
|
||||
document.getElementById('tab'+x.charAt(0).toUpperCase()+x.slice(1)).classList.toggle('on', x===t);
|
||||
});
|
||||
if(t==='country') loadCountries();
|
||||
}
|
||||
|
||||
async function loadCountries(){
|
||||
const el = document.getElementById('countryList');
|
||||
el.innerHTML = '<div class="ess-loading">正在通过 AI 加载 195 个国家...</div>';
|
||||
const r = await fetch('/api/essentials/国家', {headers:ah()});
|
||||
const d = await r.json();
|
||||
el.innerHTML = d.items.map(i => `
|
||||
<div class="ess-card" onclick="speakText('${i.en}')">
|
||||
<div><div class="ess-en">${i.en}</div><div class="ess-zh">${i.zh}</div></div>
|
||||
<div class="ess-note">${i.note}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function speakText(t){
|
||||
const s = new SpeechSynthesisUtterance(t);
|
||||
s.lang = 'en-US';
|
||||
window.speechSynthesis.speak(s);
|
||||
}
|
||||
|
||||
if(API_TOKEN) enterApp();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
#!/bin/bash
|
||||
# ══════════════════════════════════════════════════════════
|
||||
# CiBird 词鸟 安装脚本
|
||||
# 一键部署你的私人英语单词本
|
||||
# 项目地址:https://github.com/zhangyang-games/cibird
|
||||
# ══════════════════════════════════════════════════════════
|
||||
|
||||
set -e
|
||||
|
||||
# ── 颜色 ──────────────────────────────────────────────────
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
DIM='\033[2m'
|
||||
NC='\033[0m'
|
||||
|
||||
REPO_RAW="https://raw.githubusercontent.com/zhangyang-games/cibird/main"
|
||||
INSTALL_DIR="$HOME/cibird"
|
||||
|
||||
# ── Banner ────────────────────────────────────────────────
|
||||
print_banner() {
|
||||
clear
|
||||
echo ""
|
||||
echo -e "${BOLD}${CYAN}"
|
||||
echo " ██████╗██╗██████╗ ██╗██████╗ ██████╗"
|
||||
echo " ██╔════╝██║██╔══██╗██║██╔══██╗██╔══██╗"
|
||||
echo " ██║ ██║██████╔╝██║██████╔╝██║ ██║"
|
||||
echo " ██║ ██║██╔══██╗██║██╔══██╗██║ ██║"
|
||||
echo " ╚██████╗██║██████╔╝██║██║ ██║██████╔╝"
|
||||
echo " ╚═════╝╚═╝╚═════╝ ╚═╝╚═╝ ╚═╝╚═════╝"
|
||||
echo -e "${NC}"
|
||||
echo -e "${BOLD} ┌─────────────────────────────────────────────┐${NC}"
|
||||
echo -e "${BOLD} │ 🦜 CiBird 词鸟 · 你的私人英语单词本 │${NC}"
|
||||
echo -e "${DIM} │ AI造句 · 发音 · 推特/游戏英语场景 │${NC}"
|
||||
echo -e "${BOLD} └─────────────────────────────────────────────┘${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ── 步骤提示 ──────────────────────────────────────────────
|
||||
step() { echo -e "\n${BOLD}${CYAN}[$1]${NC} $2"; }
|
||||
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
||||
err() { echo -e " ${RED}✗ 错误:${NC}$1"; exit 1; }
|
||||
info() { echo -e " ${DIM}$1${NC}"; }
|
||||
ask() { echo -e " ${YELLOW}→${NC} $1"; }
|
||||
|
||||
# ── 0. 检查基础依赖 ────────────────────────────────────────
|
||||
check_deps() {
|
||||
step "0/6" "检查系统环境"
|
||||
|
||||
for cmd in curl python3; do
|
||||
if ! command -v $cmd &>/dev/null; then
|
||||
info "正在安装 $cmd ..."
|
||||
if command -v apt-get &>/dev/null; then
|
||||
apt-get update -qq && apt-get install -y $cmd -qq
|
||||
elif command -v apk &>/dev/null; then
|
||||
apk add --no-cache $cmd
|
||||
elif command -v yum &>/dev/null; then
|
||||
yum install -y $cmd
|
||||
else
|
||||
err "请手动安装 $cmd 后重试"
|
||||
fi
|
||||
fi
|
||||
ok "$cmd 已就绪"
|
||||
done
|
||||
}
|
||||
|
||||
# ── 1. 安装 Python 依赖 ────────────────────────────────────
|
||||
install_python_deps() {
|
||||
step "1/6" "安装 Python 依赖包"
|
||||
info "需要安装:fastapi uvicorn httpx"
|
||||
|
||||
# 判断是否需要 --break-system-packages(Ubuntu 22+)
|
||||
BREAK_SYS=""
|
||||
if python3 -c "import sys; exit(0 if sys.version_info>=(3,11) else 1)" 2>/dev/null; then
|
||||
BREAK_SYS="--break-system-packages"
|
||||
fi
|
||||
# 另一种判断:pip 报 externally-managed
|
||||
if pip3 install --help 2>&1 | grep -q 'break-system'; then
|
||||
BREAK_SYS="--break-system-packages"
|
||||
fi
|
||||
|
||||
python3 -m pip install --quiet fastapi uvicorn[standard] httpx $BREAK_SYS 2>/dev/null \
|
||||
|| pip3 install --quiet fastapi uvicorn[standard] httpx $BREAK_SYS 2>/dev/null \
|
||||
|| err "pip 安装失败,请手动执行:pip3 install fastapi uvicorn httpx"
|
||||
|
||||
ok "Python 依赖安装完成"
|
||||
}
|
||||
|
||||
# ── 2. 下载项目文件 ────────────────────────────────────────
|
||||
download_files() {
|
||||
step "2/6" "下载 CiBird 项目文件"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
for f in server.py index.html; do
|
||||
info "下载 $f ..."
|
||||
curl -fsSL "$REPO_RAW/$f" -o "$INSTALL_DIR/$f" \
|
||||
|| err "下载 $f 失败,请检查网络或 GitHub 地址"
|
||||
ok "$f 已下载"
|
||||
done
|
||||
}
|
||||
|
||||
# ── 3. 选择 AI 服务商 ──────────────────────────────────────
|
||||
choose_provider() {
|
||||
step "3/6" "选择 AI 服务商"
|
||||
echo ""
|
||||
echo -e " ${BOLD}请选择你要使用的 AI 服务商:${NC}"
|
||||
echo ""
|
||||
echo -e " ${GREEN}1)${NC} Google Gemini ${DIM}← 免费额度大,推荐新手${NC}"
|
||||
echo -e " ${GREEN}2)${NC} DeepSeek ${DIM}← 国产之光,便宜效果好${NC}"
|
||||
echo -e " ${GREEN}3)${NC} Groq ${DIM}← 速度极快,免费${NC}"
|
||||
echo -e " ${GREEN}4)${NC} OpenRouter ${DIM}← 一个Key用几十种模型${NC}"
|
||||
echo -e " ${GREEN}5)${NC} Claude ${DIM}← Anthropic,你有付费Key${NC}"
|
||||
echo -e " ${GREEN}6)${NC} OpenAI ${DIM}← ChatGPT 同款${NC}"
|
||||
echo ""
|
||||
read -p " 👉 输入编号 [1-6]:" PROVIDER_CHOICE
|
||||
|
||||
case "$PROVIDER_CHOICE" in
|
||||
1)
|
||||
PROVIDER="gemini"
|
||||
MODEL="gemini-2.0-flash"
|
||||
info "获取 Key:https://aistudio.google.com/apikey"
|
||||
;;
|
||||
2)
|
||||
PROVIDER="deepseek"
|
||||
MODEL="deepseek-chat"
|
||||
info "获取 Key:https://platform.deepseek.com"
|
||||
;;
|
||||
3)
|
||||
PROVIDER="groq"
|
||||
MODEL="llama-3.1-8b-instant"
|
||||
info "获取 Key:https://console.groq.com"
|
||||
;;
|
||||
4)
|
||||
PROVIDER="openrouter"
|
||||
MODEL="google/gemini-flash-1.5"
|
||||
info "获取 Key:https://openrouter.ai"
|
||||
;;
|
||||
5)
|
||||
PROVIDER="claude"
|
||||
MODEL="claude-haiku-4-5-20251001"
|
||||
info "获取 Key:https://console.anthropic.com"
|
||||
;;
|
||||
6)
|
||||
PROVIDER="openai"
|
||||
MODEL="gpt-4o-mini"
|
||||
info "获取 Key:https://platform.openai.com"
|
||||
;;
|
||||
*)
|
||||
err "无效选择,请输入 1-6"
|
||||
;;
|
||||
esac
|
||||
|
||||
ok "已选择:$PROVIDER(模型:$MODEL)"
|
||||
|
||||
echo ""
|
||||
read -p " 👉 请粘贴你的 API Key:" API_KEY
|
||||
[ -z "$API_KEY" ] && err "API Key 不能为空"
|
||||
ok "API Key 已录入"
|
||||
|
||||
echo ""
|
||||
echo -e " ${DIM}💡 如果你想用其他模型,可以直接改默认值,也可以回车使用默认${NC}"
|
||||
read -p " 👉 模型名称(默认 $MODEL,直接回车跳过):" MODEL_INPUT
|
||||
[ -n "$MODEL_INPUT" ] && MODEL="$MODEL_INPUT"
|
||||
ok "模型:$MODEL"
|
||||
}
|
||||
|
||||
# ── 4. 设置访问密码 ────────────────────────────────────────
|
||||
setup_auth() {
|
||||
step "4/6" "设置登录账号"
|
||||
|
||||
echo ""
|
||||
read -p " 👉 用户名(默认 admin,回车跳过):" USERNAME
|
||||
[ -z "$USERNAME" ] && USERNAME="admin"
|
||||
ok "用户名:$USERNAME"
|
||||
|
||||
echo ""
|
||||
while true; do
|
||||
read -s -p " 👉 设置登录密码(至少6位):" PASSWORD
|
||||
echo ""
|
||||
[ ${#PASSWORD} -ge 6 ] && break
|
||||
echo -e " ${RED}密码太短,请至少6位!${NC}"
|
||||
done
|
||||
read -s -p " 👉 再次确认密码:" PASSWORD2
|
||||
echo ""
|
||||
[ "$PASSWORD" != "$PASSWORD2" ] && err "两次密码不一致"
|
||||
ok "密码已设置"
|
||||
|
||||
echo ""
|
||||
read -p " 👉 访问端口(默认 8848,回车跳过):" PORT
|
||||
[ -z "$PORT" ] && PORT="8848"
|
||||
ok "端口:$PORT"
|
||||
|
||||
# 生成密码 hash
|
||||
PASSWORD_HASH=$(python3 -c "import hashlib; print(hashlib.sha256('$PASSWORD'.encode()).hexdigest())")
|
||||
}
|
||||
|
||||
# ── 5. 写入配置 ────────────────────────────────────────────
|
||||
write_config() {
|
||||
step "5/6" "写入配置文件"
|
||||
|
||||
cat > "$INSTALL_DIR/config.json" <<EOF
|
||||
{
|
||||
"username": "$USERNAME",
|
||||
"password_hash": "$PASSWORD_HASH",
|
||||
"port": $PORT,
|
||||
"provider": "$PROVIDER",
|
||||
"api_key": "$API_KEY",
|
||||
"model": "$MODEL"
|
||||
}
|
||||
EOF
|
||||
chmod 600 "$INSTALL_DIR/config.json"
|
||||
ok "config.json 已写入(权限已设为仅自己可读)"
|
||||
}
|
||||
|
||||
# ── 6. 启动服务 ────────────────────────────────────────────
|
||||
start_service() {
|
||||
step "6/6" "启动 CiBird 服务"
|
||||
|
||||
# 停掉旧进程
|
||||
if [ -f "$INSTALL_DIR/cibird.pid" ]; then
|
||||
OLD_PID=$(cat "$INSTALL_DIR/cibird.pid")
|
||||
kill "$OLD_PID" 2>/dev/null && info "已停止旧进程 (PID $OLD_PID)" || true
|
||||
rm -f "$INSTALL_DIR/cibird.pid"
|
||||
fi
|
||||
# 也搜一下
|
||||
pkill -f "server.py" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# 初始化数据库
|
||||
cd "$INSTALL_DIR"
|
||||
python3 -c "
|
||||
import sys; sys.path.insert(0,'.')
|
||||
from server import init_db
|
||||
init_db()
|
||||
print(' DB 初始化完成')
|
||||
"
|
||||
# 后台启动
|
||||
nohup python3 "$INSTALL_DIR/server.py" >> "$INSTALL_DIR/cibird.log" 2>&1 &
|
||||
echo $! > "$INSTALL_DIR/cibird.pid"
|
||||
sleep 2
|
||||
|
||||
# 验证是否启动成功
|
||||
if kill -0 $(cat "$INSTALL_DIR/cibird.pid") 2>/dev/null; then
|
||||
ok "服务启动成功!(PID $(cat $INSTALL_DIR/cibird.pid))"
|
||||
else
|
||||
err "服务启动失败,查看日志:cat $INSTALL_DIR/cibird.log"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── 写入快捷命令 ───────────────────────────────────────────
|
||||
write_shortcut() {
|
||||
SHORTCUT="/usr/local/bin/cibird"
|
||||
cat > "$SHORTCUT" <<'SCRIPT'
|
||||
#!/bin/bash
|
||||
INSTALL_DIR="$HOME/cibird"
|
||||
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'
|
||||
|
||||
get_pid() { [ -f "$INSTALL_DIR/cibird.pid" ] && cat "$INSTALL_DIR/cibird.pid" || echo ""; }
|
||||
is_running() { PID=$(get_pid); [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; }
|
||||
get_port() { python3 -c "import json; print(json.load(open('$INSTALL_DIR/config.json'))['port'])" 2>/dev/null || echo "8848"; }
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if is_running; then
|
||||
echo -e " ${YELLOW}词鸟已经在运行中${NC}"
|
||||
else
|
||||
cd "$INSTALL_DIR"
|
||||
nohup python3 server.py >> cibird.log 2>&1 &
|
||||
echo $! > cibird.pid
|
||||
sleep 1
|
||||
echo -e " ${GREEN}✓ 词鸟已启动${NC} 访问 http://$(curl -s ifconfig.me 2>/dev/null || echo '你的IP'):$(get_port)"
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
PID=$(get_pid)
|
||||
[ -n "$PID" ] && kill "$PID" 2>/dev/null && echo -e " ${GREEN}✓ 词鸟已停止${NC}" || echo -e " ${DIM}没有运行中的词鸟${NC}"
|
||||
rm -f "$INSTALL_DIR/cibird.pid"
|
||||
;;
|
||||
restart)
|
||||
$0 stop; sleep 1; $0 start
|
||||
;;
|
||||
log)
|
||||
tail -f "$INSTALL_DIR/cibird.log"
|
||||
;;
|
||||
status)
|
||||
PORT=$(get_port)
|
||||
if is_running; then
|
||||
echo -e " ${GREEN}● 词鸟运行中${NC} PID $(get_pid)"
|
||||
echo -e " ${DIM}访问地址:http://$(curl -s ifconfig.me 2>/dev/null || echo '你的IP'):$PORT${NC}"
|
||||
else
|
||||
echo -e " ${RED}○ 词鸟未运行${NC}"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
echo -e " ${BOLD}🦜 CiBird 词鸟 管理命令${NC}"
|
||||
echo ""
|
||||
PORT=$(get_port)
|
||||
if is_running; then
|
||||
echo -e " ${GREEN}● 状态:运行中${NC} PID $(get_pid)"
|
||||
else
|
||||
echo -e " ${RED}○ 状态:未运行${NC}"
|
||||
fi
|
||||
echo -e " ${DIM}访问地址:http://你的IP:$PORT${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}cibird start${NC} 启动"
|
||||
echo -e " ${CYAN}cibird stop${NC} 停止"
|
||||
echo -e " ${CYAN}cibird restart${NC} 重启"
|
||||
echo -e " ${CYAN}cibird status${NC} 查看状态"
|
||||
echo -e " ${CYAN}cibird log${NC} 查看日志"
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
SCRIPT
|
||||
chmod +x "$SHORTCUT" 2>/dev/null || true
|
||||
ok "快捷命令 cibird 已安装(可用 cibird start/stop/status)"
|
||||
}
|
||||
|
||||
# ── 获取公网 IP ────────────────────────────────────────────
|
||||
get_public_ip() {
|
||||
PUBLIC_IP=$(curl -s --connect-timeout 5 ifconfig.me 2>/dev/null \
|
||||
|| curl -s --connect-timeout 5 api.ipify.org 2>/dev/null \
|
||||
|| echo "你的VPS公网IP")
|
||||
}
|
||||
|
||||
|
||||
# ── 导入单词 ───────────────────────────────────────────────
|
||||
import_words() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}📖 导入多邻国单词本${NC}"
|
||||
echo -e " ${DIM}从 GitHub 拉取 words.txt 导入到词库${NC}"
|
||||
echo ""
|
||||
WORDS_URL="https://raw.githubusercontent.com/zhangyang-games/cibird/main/words.txt"
|
||||
curl -sL "$WORDS_URL" | python3 -c "
|
||||
import sys,sqlite3,pathlib
|
||||
DB=pathlib.Path.home()/'cibird/cibird.db'
|
||||
if not DB.exists(): print(' 找不到数据库'); exit(1)
|
||||
words=[l.strip().split('|',1) for l in sys.stdin if '|' in l]
|
||||
conn=sqlite3.connect(DB)
|
||||
existing=set(r[0].lower() for r in conn.execute('SELECT word FROM words'))
|
||||
added=skipped=0
|
||||
for w in words:
|
||||
if len(w)!=2: continue
|
||||
word,meaning=w
|
||||
if word.lower() in existing: skipped+=1; continue
|
||||
conn.execute('INSERT INTO words(word,meaning,phonetic,pos,examples,note) VALUES(?,?,?,?,?,?)',(word,meaning,'','','[]',''))
|
||||
existing.add(word.lower()); added+=1
|
||||
conn.commit(); conn.close()
|
||||
print(f' ✓ 导入完成!新增 {added} 个,跳过 {skipped} 个')
|
||||
" || echo -e " ${RED}✗ 导入失败,请检查网络${NC}"
|
||||
}
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
print_banner
|
||||
|
||||
echo -e " 欢迎!这个脚本会帮你在 VPS 上部署 CiBird 词鸟。"
|
||||
echo -e " ${DIM}整个过程大约需要 2-3 分钟,请跟着提示一步步来。${NC}"
|
||||
echo ""
|
||||
read -p " 按回车键开始安装,Ctrl+C 取消..." _
|
||||
|
||||
check_deps
|
||||
install_python_deps
|
||||
download_files
|
||||
choose_provider
|
||||
setup_auth
|
||||
write_config
|
||||
start_service
|
||||
write_shortcut
|
||||
get_public_ip
|
||||
|
||||
# ── 可选:导入单词 ─────────────────────────────────────────
|
||||
echo ""
|
||||
read -p " 👉 是否导入你的多邻国单词本?(输入 y 导入,回车跳过):" IMPORT_CHOICE
|
||||
if [ "$IMPORT_CHOICE" = "y" ] || [ "$IMPORT_CHOICE" = "Y" ]; then
|
||||
import_words
|
||||
fi
|
||||
|
||||
# ── 完成提示 ──────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}${GREEN}"
|
||||
echo " ┌──────────────────────────────────────────────┐"
|
||||
echo " │ 🎉 CiBird 词鸟安装完成! │"
|
||||
echo " └──────────────────────────────────────────────┘"
|
||||
echo -e "${NC}"
|
||||
echo -e " ${BOLD}🌐 访问地址:${CYAN}http://$PUBLIC_IP:$PORT${NC}"
|
||||
echo -e " ${BOLD}👤 用户名:${NC}$USERNAME"
|
||||
echo -e " ${BOLD}🔑 密码:${NC}你刚才设置的密码"
|
||||
echo ""
|
||||
echo -e " ${DIM}💡 管理命令:${NC}"
|
||||
echo -e " ${DIM} cibird 查看状态${NC}"
|
||||
echo -e " ${DIM} cibird stop 停止服务${NC}"
|
||||
echo -e " ${DIM} cibird start 启动服务${NC}"
|
||||
echo -e " ${DIM} cibird log 查看日志(排查问题用)${NC}"
|
||||
echo ""
|
||||
echo -e " ${DIM}⚠️ 如果访问不了,请确认防火墙已开放 $PORT 端口${NC}"
|
||||
echo -e " ${DIM} Oracle Cloud 需要在安全列表里手动添加规则${NC}"
|
||||
echo ""
|
||||
echo -e " ${BOLD}展翅高飞!🦜${NC}"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user