Gemini 全能 QQ 机器人部署手册 (V1.0 Release)
核心架构 :OneBot V11 (NapCat) + NoneBot2 + Gemini Flash
适用系统:Ubuntu 22.04 LTS (阿里云/腾讯云)
🟢 第一阶段:基础设施准备
SSH 连接服务器后,复制以下命令执行。
-
安装必要软件 (Docker + Python)
bash# 更新软件源 sudo apt update && sudo apt upgrade -y # 安装 Docker curl -fsSL https://get.docker.com | bash # 安装 Python3 及虚拟环境工具 sudo apt install python3-pip python3-venv -y # 创建项目文件夹 mkdir -p /root/gemini_bot cd /root/gemini_bot
🔌 第二阶段:部署 QQ 协议端 (NapCat)
【避坑重点】 :这里我们使用 --net=host 模式启动。这样容器和宿主机共用网络,配置 WebSocket 时直接填 127.0.0.1 即可,彻底解决连接不上、找不到 IP 的问题。
-
启动 NapCat 容器
(请将下方命令中的你的QQ号替换为实际数字)bashdocker run -d \ --name napcat \ --net=host \ --restart always \ -e ACCOUNT=你的QQ号 \ -v /root/gemini_bot/napcat/config:/app/config \ mlikiowa/napcat-docker:latest -
放行防火墙端口
- 请去阿里云/腾讯云控制台的"防火墙"或"安全组",放行 TCP 6099 和 TCP 8080。
🧠 第三阶段:部署核心代码
【避坑重点】 :这里我们锁定安装 0.3.2 版本的 Google 库,这是目前配合此代码最稳定的版本,没有任何属性报错。
-
配置 Python 环境
bash# 确保在项目目录 cd /root/gemini_bot # 创建虚拟环境 python3 -m venv venv # 激活环境 source venv/bin/activate # 安装依赖 (锁定版本,拒绝报错) pip install nonebot2[fastapi] nonebot-adapter-onebot google-generativeai==0.3.2 pytz -
创建
memory.py(纯净版)- 此版本修复了全角空格问题。
- 此版本修复了回复带时间前缀的问题。
- 此版本使用了同步接口+Async包装,兼容性最强。
使用
nano memory.py创建文件,填入你的 API Key:
python
# 恢复旧版导入(关键!)
import google.generativeai as genai
import asyncio
import datetime
import pytz
import pickle
import os
import random
# ================= 核心配置区 =================
API_KEY = "在这里粘贴你的Google_API_Key" # 必须替换!
DATA_FILE = "bot_memory.pkl"
# --- 拟人化参数 (V1.0 最终版) ---
COOLDOWN_SECONDS = 300 # 主动说话冷却 (5分钟)
TRIGGER_PROBABILITY = 0.015 # 闲聊插嘴概率 (1.5%)
REPEATER_THRESHOLD = 2 # 复读检测阈值 (至少2人)
REPEATER_EXEC_PROB = 0.1 # 复读执行概率 (10%,防止刷屏)
MEMORY_LIMIT_CHARS = 10000 # 缓存字数上限
MEMORY_KEEP_COUNT = 100 # 压缩时保留最近 200 条
# ============================================
# 旧版兼容写法(0.3.2 支持)
genai.configure(api_key=API_KEY)
# 移除无效的 tools 配置(避免报错),联网功能由模型自动处理
chat_model = genai.GenerativeModel('gemini-2.5-flash')
class GroupMemory:
def __init__(self, group_id):
self.group_id = group_id
self.summary = "暂无早期历史记录。"
self.buffer = []
self.lock = asyncio.Lock()
# 状态追踪
self.last_active_time = 0
self.last_msg_content = ""
self.repeat_count = 0
def add_message(self, name, msg, time_str):
entry = f"[{time_str}] 【{name}】: {msg}"
self.buffer.append(entry)
# 复读计数
clean_msg = msg.strip()
if clean_msg == self.last_msg_content and clean_msg:
self.repeat_count += 1
else:
self.last_msg_content = clean_msg
self.repeat_count = 1
def estimate_length(self):
return sum(len(m) for m in self.buffer)
async def check_and_compress(self):
"""安全压缩逻辑:API成功才删旧数据"""
async with self.lock:
if self.estimate_length() > MEMORY_LIMIT_CHARS and len(self.buffer) > MEMORY_KEEP_COUNT:
to_compress = self.buffer[:-MEMORY_KEEP_COUNT]
text_block = "\n".join(to_compress)
prompt = f"""
你是记忆整理员,更新长期记忆摘要(800字内):
【长期记忆】:{self.summary}
【待归档对话】:{text_block}
"""
try:
# 同步接口 + asyncio 包装(避免异步兼容问题)
loop = asyncio.get_running_loop()
resp = await loop.run_in_executor(None, lambda: chat_model.generate_content(prompt))
if resp.candidates[0].content.parts[0].text:
self.summary = resp.candidates[0].content.parts[0].text
self.buffer = self.buffer[-MEMORY_KEEP_COUNT:]
save_data()
except Exception as e:
print(f"压缩记忆失败:{e}")
pass
async def generate_reply(self, current_question, sender_name, is_active_interrupt=False):
tz = pytz.timezone('Asia/Shanghai')
context_str = "\n".join(self.buffer)
# 构建回复提示词
if is_active_interrupt:
task_prompt = "没人艾特你,自然加入讨论,简短、像群友,可复读/玩梗,直接说话不加称呼,绝对不要输出任何时间戳/日期/时间格式。"
else:
if not current_question.strip():
task_prompt = f"用户【{sender_name}】艾特了你,自然接话,绝对不要输出任何时间戳/日期/时间格式。"
else:
task_prompt = f"用户【{sender_name}】提问:{current_question},请回答,绝对不要输出任何时间戳/日期/时间格式。"
final_prompt = f"""
你是QQ群里的AI伙伴Gemini。
【长期记忆】:{self.summary}
【近期对话】:{context_str}
【任务】:{task_prompt}
【规则】:问时间时只说当前北京时间(比如"现在是下午3点20分"),但不要输出任何括号/时间戳/数字格式的时间;问新闻/数据自动联网,风格像群友而非客服。
"""
try:
loop = asyncio.get_running_loop()
resp = await loop.run_in_executor(None, lambda: chat_model.generate_content(final_prompt))
return resp.candidates[0].content.parts[0].text if resp.candidates[0].content.parts[0].text else "(暂时无法回复)"
except Exception as e:
return f"(回复失败:{str(e)})"
async def check_active_intervention(self, current_msg):
"""主动介入判断:冷却→复读→闲聊→AI决策"""
import time
now = time.time()
# 1. 冷却判断
if now - self.last_active_time < COOLDOWN_SECONDS:
return False, None
# 2. 智能复读(10%概率)
if self.repeat_count >= REPEATER_THRESHOLD and random.random() < REPEATER_EXEC_PROB:
try:
loop = asyncio.get_running_loop()
check_resp = await loop.run_in_executor(None,
lambda: chat_model.generate_content(f"判断'{current_msg}'是否是正常梗(非广告/脏话):是回YES,否回NO")
)
if "YES" in check_resp.candidates[0].content.parts[0].text.strip().upper():
self.last_active_time = now
return True, current_msg
except:
pass
return False, None
# 3. 闲聊插嘴(1.5%概率)
if "gemini" not in current_msg.lower() and random.random() > TRIGGER_PROBABILITY:
return False, None
# 4. AI决策是否说话
try:
loop = asyncio.get_running_loop()
decision_resp = await loop.run_in_executor(None,
lambda: chat_model.generate_content(f"""
最近消息:{chr(10).join(self.buffer[-15:])}
当前消息:{current_msg}
没被艾特,是否有必要说话?满足(客观问题无人答/讨论你/有趣梗)回YES,否则NO
""")
)
if "YES" in decision_resp.candidates[0].content.parts[0].text.strip().upper():
reply = await self.generate_reply("", "群友", is_active_interrupt=True)
self.last_active_time = now
return True, reply
except Exception as e:
print(f"主动介入失败:{e}")
pass
return False, None
# --- 记忆持久化 ---
memories = {}
def save_data():
try:
with open(DATA_FILE, 'wb') as f:
pickle.dump(memories, f)
except Exception as e:
print(f"保存记忆失败:{e}")
def load_data():
global memories
if os.path.exists(DATA_FILE):
try:
with open(DATA_FILE, 'rb') as f:
memories = pickle.load(f)
except:
memories = {}
def get_memory(group_id):
if group_id not in memories:
memories[group_id] = GroupMemory(group_id)
return memories[group_id]
load_data()
- 创建
bot.py- 使用
nano bot.py创建:
- 使用
python
import nonebot
from nonebot import on_message
from nonebot.adapters.onebot.v11 import Adapter, Bot, GroupMessageEvent
from memory import get_memory, save_data
import atexit
import datetime
import pytz
nonebot.init()
driver = nonebot.get_driver()
driver.register_adapter(Adapter)
atexit.register(save_data)
def unix_to_beijing(timestamp):
dt_utc = datetime.datetime.fromtimestamp(timestamp, pytz.utc)
dt_bj = dt_utc.astimezone(pytz.timezone('Asia/Shanghai'))
return dt_bj.strftime("%H:%M:%S")
def get_current_bj_time():
return datetime.datetime.now(pytz.timezone('Asia/Shanghai')).strftime("%H:%M:%S")
def is_at_me(bot: Bot, event: GroupMessageEvent) -> bool:
if event.is_tome(): return True
for seg in event.message:
if seg.type == "at" and str(seg.data.get("qq")) == str(bot.self_id):
return True
return False
# === 单核处理器 ===
handler = on_message(priority=1, block=True)
@handler.handle()
async def _(bot: Bot, event: GroupMessageEvent):
group_id = event.group_id
user_id = event.user_id
msg = event.get_plaintext().strip()
if not msg: msg = "[非文本消息]"
try:
info = await bot.get_group_member_info(group_id=group_id, user_id=user_id)
name = info.get('card') or info.get('nickname') or str(user_id)
except: name = "群友"
# 1. 记录用户消息
mem = get_memory(group_id)
mem.add_message(name, msg, unix_to_beijing(event.time))
save_data()
# 2. 压缩检查
await mem.check_and_compress()
# 3. 决策逻辑
final_reply = None
if is_at_me(bot, event):
# 情况A:被艾特 -> 必回
# 这里的 msg 可能包含 @机器人,Gemini处理时会自动忽略或理解为名字,无需刻意剔除
final_reply = await mem.generate_reply(msg, name, is_active_interrupt=False)
else:
# 情况B:没被艾特 -> 尝试插嘴
should, content = await mem.check_active_intervention(msg)
if should and content:
final_reply = content
# 4. 如果有回复,发送并记录
if final_reply:
# 记录机器人自己的回复
mem.add_message("Gemini", final_reply, get_current_bj_time())
save_data()
# 发送
await handler.finish(final_reply)
if __name__ == "__main__":
nonebot.run(host="0.0.0.0", port=8080)
⚙️ 第四阶段:配置系统服务 (Systemd)
为了实现您要求的命令管理,我们需要创建一个系统服务文件。
-
创建服务文件
bashsudo nano /etc/systemd/system/gemini-bot.service -
粘贴内容
(注意:ExecStart 指向了虚拟环境中的 python,确保路径准确)ini[Unit] Description=Gemini QQ Bot Service After=network.target [Service] # 指定用户,通常是 root User=root # 你的项目路径 WorkingDirectory=/root/gemini_bot # 使用虚拟环境的 Python 启动 bot.py ExecStart=/root/gemini_bot/venv/bin/python bot.py # 自动重启配置 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target -
加载服务
bashsudo systemctl daemon-reload sudo systemctl enable gemini-bot
🖥️ 第五阶段:NapCat 连接 (WebUI)
【避坑重点】 :因为我们用了 host 模式,这里的 WS 地址必须填 127.0.0.1。
-
启动机器人
bashsystemctl start gemini-bot -
浏览器访问 NapCat
访问
http://服务器IP:6099/webui
(如果没有Token,输入napcat或查看日志docker logs napcat) -
扫码登录
使用手机 QQ 扫码。
-
配置 WebSocket (Reverse)
- 点击左侧 网络配置 -> WebSocket (Reverse)
- URL 填写 :
ws://127.0.0.1:8080/onebot/v11/ws - 启用:打钩
- 点击 保存。
-
重启 NapCat 生效
bashdocker restart napcat
🎮 最终:管理命令速查表
恭喜!部署完成。以后您只需要使用以下命令管理您的机器人:
| 功能 | 命令 |
|---|---|
| 启动机器人 | systemctl start gemini-bot |
| 停止机器人 | systemctl stop gemini-bot |
| 改代码后重启 | systemctl restart gemini-bot |
| 查看运行状态 | systemctl status gemini-bot |
| 实时查看日志 | journalctl -u gemini-bot -f |
(日志中若看到 Connect to 127.0.0.1:8080 或 Received message 即为成功)