跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python

Gemini 全能 QQ 机器人部署手册 (V1.0 Release)

Gemini 全能 QQ 机器人部署手册 (V1.0 Release) **核心架构**:OneBot V11 (NapCat) + NoneBot2 + Gemini Flash **适用系统**:Ubuntu 22.04 LTS (阿里云/腾讯云) * * 🟢 第一阶段:基础设施准备 SSH 连接服务器后,复制以下命令执行。 **安装必要软件 (Docker + Python)** * *…

星辰大海发布于 2026/4/6更新于 2026/7/2343K 浏览

Gemini 全能 QQ 机器人部署手册 (V1.0 Release)

核心架构:OneBot V11 (NapCat) + NoneBot2 + Gemini Flash
适用系统:Ubuntu 22.04 LTS (阿里云/腾讯云)


🟢 第一阶段:基础设施准备

SSH 连接服务器后,复制以下命令执行。

安装必要软件 (Docker + Python)

# 更新软件源sudoapt update &&sudoapt upgrade -y# 安装 Dockercurl-fsSL https://get.docker.com |bash# 安装 Python3 及虚拟环境工具sudoaptinstall python3-pip python3-venv -y# 创建项目文件夹mkdir-p /root/gemini_bot cd /root/gemini_bot 

🔌 第二阶段:部署 QQ 协议端 (NapCat)

【避坑重点】:这里我们使用 --net=host 模式启动。这样容器和宿主机共用网络,配置 WebSocket 时直接填 127.0.0.1 即可,彻底解决连接不上、找不到 IP 的问题。

  1. 放行防火墙端口
    • 请去阿里云/腾讯云控制台的'防火墙'或'安全组',放行 TCP 6099 和 TCP 8080。

启动 NapCat 容器
(请将下方命令中的 你的QQ号 替换为实际数字)

docker run -d\--name napcat \--net=host \--restart always \-eACCOUNT=你的QQ号 \-v /root/gemini_bot/napcat/config:/app/config \ mlikiowa/napcat-docker:latest 

🧠 第三阶段:部署核心代码

【避坑重点】:这里我们锁定安装 0.3.2 版本的 Google 库,这是目前配合此代码最稳定的版本,没有任何属性报错。

  1. 创建 memory.py
    • 使用 nano memory.py 创建文件,填入你的 API Key:

配置 Python 环境

# 确保在项目目录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 
# 恢复旧版导入(关键!)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"# ============================================# 旧版兼容写法(. 支持) genai(api_key=API_KEY) chat_model = genai('gemini--flash')classGroupMemory:(self, group_id): self.group_id = group_id self.summary = self.buffer=[] self.lock = asyncio.()# 异步锁(无法序列化)# 状态追踪 self.last_active_time = self.last_msg_content = self.repeat_count =# ===== 每个群独立的参数配置 ===== self.bot_config ={"COOLDOWN_SECONDS":,# 主动说话冷却 (分钟):,# 闲聊插嘴概率 ():,# 复读检测阈值 (至少人):,# 复读执行概率 (,防止刷屏):,# 缓存字数上限:# 压缩时保留最近  条}# ===== 修复:打卡数据存储(移除lambda,改用普通字典)=====# 结构:{用户ID: {"name": 用户名, : 总星星数, : {打卡内容: 次数}}} self ={}# 普通字典,不再用defaultdict(lambda)# ===== 新增:自定义序列化/反序列化方法(跳过Lock)=====def__getstate__(self):# 序列化时,排除lock对象,返回其他需要保存的属性 state = self.__dict__.()del state[]# 移除无法序列化的Lockreturn state (self, state):# 反序列化时,恢复所有属性,并重新初始化Lock self.__dict__.(state) self.lock = asyncio.()# 重新创建Lock对象# ========== 修复:打卡功能方法(手动初始化默认值)==========(self, user_id, user_name, content):# 手动初始化用户数据(无lambda,兼容pickle)if user_id notin self.checkin_data: self.checkin_data[user_id]={"name":,:,:{}# 普通字典存打卡内容} user_data = self user_data= user_name # 更新最新昵称# 手动初始化打卡内容if  notin user_data: user_data[][content]= user_data[][content]+= user_data[]+=# 次打卡=颗星 ()# 立即持久化(self, user_id, content):if user_id notin self.checkin_data:returnFalse, user_data = self.checkin_data[user_id]if content notin user_data[]or user_data[][content]==:returnFalse,f# 减少次数 user_data[][content]-= user_data[]-=# 内容次数为则移除该记录if user_data[][content]==:del user_data[][content]# 无任何记录则移除用户ifnot user_data[]:del self.checkin_data[user_id] ()returnTrue,f# ========== 管理员管理他人打卡记录的方法 ==========(self, target_user_id, target_user_name, content, count, operation):adddelete# . 初始化目标用户数据if target_user_id notin self.checkin_data: self.checkin_data[target_user_id]={"name": target_user_name,:,:{}} user_data = self user_data= target_user_name # 更新目标用户昵称# . 初始化打卡内容if  notin user_data: user_data[][content]=# . 执行增加/删除操作if operation ==: user_data[][content]+= count user_data[]+= count ()returnTrue,felif operation ==:if user_data[][content]< count:returnFalse,f user_data[][content]-= count user_data[]-= count # 内容次数为则移除该记录if user_data[][content]==:del user_data[][content]# 无任何记录则移除用户ifnot user_data[]:del self.checkin_data[target_user_id] ()returnTrue,felse:returnFalse,(self, stars): crown = stars //(**)# 皇冠:星 stars %=(**) sun = stars //(*)# 太阳:星 stars %=(*) moon = stars //# 月亮:星 star = stars %# 剩余星星 medals =if crown >: medals +=* crown if sun >: medals +=* sun if moon >: medals +=* moon if star >(crown+sun+moon+star ==): medals +=* star return medals (self):ifnot self.checkin_data:return# 按总星星排序(兼容普通字典) sorted_users =( self.checkin_data.(), key=lambda x: x[], reverse=True)# 格式化输出 ranking =for idx, user (sorted_users,): total_medals = self.(user[]) ranking +=f# 子项显示对应的勋章for content, count in user[].(): content_medals = self.(count) ranking +=freturn ranking # 原有方法(不变)(self, name, msg, time_str): entry =f self.buffer.(entry) clean_msg = msg.()if clean_msg == self.last_msg_content and clean_msg: self.repeat_count +=else: self.last_msg_content = clean_msg self.repeat_count =(self):((m)for m in self.buffer)(self):asyncwith self.lock:if self.()> self.bot_config[](self.buffer)> self.bot_config[]: to_compress = self.buffer[:-self.bot_config[]] text_block =.(to_compress) prompt =ftry: loop = asyncio.() resp =await loop.(None,lambda: chat_model.(prompt))if resp.candidates[].content.parts[].text: self.summary = resp.candidates[].content.parts[].text self.buffer= self.buffer[-self.bot_config[]:] ()except Exception as e:(f)(self, current_question, sender_name, is_active_interrupt=False): tz = pytz.() context_str =.(self.buffer)# 构建回复提示词(补充的限制)if is_active_interrupt: task_prompt =else:ifnot current_question.(): task_prompt =felse: task_prompt =f final_prompt =ftry: loop = asyncio.() resp =await loop.(None,lambda: chat_model.(final_prompt))return resp.candidates[].content.parts[].text if resp.candidates[].content.parts[].text elseexcept Exception as e:returnf(self, current_msg):import time now = time.()if now - self.last_active_time < self.bot_config[]:returnFalse,(self.repeat_count >= self.bot_config[]and random.()< self.bot_config[]and current_msg.()):try: loop = asyncio.() check_resp =await loop.(None,lambda: chat_model.(f))ifin check_resp.candidates[].content.parts[].text.().(): self.last_active_time = now returnTrue, current_msg except:passreturnFalse,Noneifnotin current_msg.()and random.()> self.bot_config[]:returnFalse,Nonetry: loop = asyncio.() decision_resp =await loop.(None,lambda: chat_model.(f))ifin decision_resp.candidates[].content.parts[].text.().(): reply =await self.(,, is_active_interrupt=True) self.last_active_time = now returnTrue, reply except Exception as e:(f)passreturnFalse,None# --- 记忆持久化(不变)--- memories ={}defsave_data():try:(DATA_FILE,)as f: pickle.(memories, f)except Exception as e:(f)():global memories if os.path.(DATA_FILE):try:(DATA_FILE,)as f: memories = pickle.(f)except: memories ={}defget_memory(group_id):if group_id notin memories: memories[group_id]= (group_id)return memories[group_id] ()
.pkl
0.3
2
.configure
.GenerativeModel
2.5
def__init__
"暂无早期历史记录。"
Lock
0
""
0
0
0
"TRIGGER_PROBABILITY"
0.05
5%
"REPEATER_THRESHOLD"
2
2
"REPEATER_EXEC_PROB"
0.1
10%
"MEMORY_LIMIT_CHARS"
10000
"MEMORY_KEEP_COUNT"
100
100
"total_stars"
"records"
.checkin_data
copy
'lock'
def__setstate__
update
Lock
defcheckin
""
"执行打卡:增加记录+总星星"
""
""
"total_stars"
0
"records"
.checkin_data
[user_id]
["name"]
content
["records"]
"records"
0
"records"
1
"total_stars"
1
1
1
save_data
defdelete_checkin
""
"删除打卡:减少记录+总星星(返回:是否成功, 提示语)"
""
"你还没有任何打卡记录哦~"
"records"
"records"
0
"你没有「{content}」的打卡记录~"
"records"
1
"total_stars"
1
0
"records"
0
"records"
"records"
save_data
"已删除「{content}」的1次打卡记录~"
defadmin_checkin
""
" 管理员操作他人打卡记录 operation: "
"(增加) / "
"(删除) 返回:是否成功, 提示语 "
""
1
"total_stars"
0
"records"
.checkin_data
[target_user_id]
["name"]
2
content
["records"]
"records"
0
3
"add"
"records"
"total_stars"
save_data
"已为用户【{target_user_name}】增加「{content}」打卡记录 {count} 次~"
"delete"
"records"
"用户【{target_user_name}】的「{content}」打卡记录不足 {count} 次!"
"records"
"total_stars"
0
"records"
0
"records"
"records"
save_data
"已为用户【{target_user_name}】删除「{content}」打卡记录 {count} 次~"
"操作类型错误!仅支持「增加」或「删除」"
defcalculate_medals
""
"计算星星对应的勋章:4星=1月,4月=1日,4日=1皇冠"
""
4
4
4
64
4
4
4
4
4
16
4
4
4
4
4
""
0
"👑"
0
"☀️"
0
"🌙"
0
or
0
"⭐"
defgenerate_checkin_ranking
""
"生成打卡排行榜(按总星星降序,子项也显示勋章)"
""
"当前还没有打卡记录哦~快叫群友一起打卡吧!"
sorted
values
"total_stars"
"🏆 打卡记录排行榜 🏆\n"
inenumerate
1
calculate_medals
"total_stars"
"{idx}. {user['name']}{total_medals}\n"
"records"
items
calculate_medals
" · {content}{content_medals}\n"
defadd_message
"[{time_str}] 【{name}】: {msg}"
append
strip
1
1
defestimate_length
returnsum
len
asyncdefcheck_and_compress
estimate_length
"MEMORY_LIMIT_CHARS"
andlen
"MEMORY_KEEP_COUNT"
"MEMORY_KEEP_COUNT"
"\n"
join
""
" 你是记忆整理员,更新长期记忆摘要(800字内): 【长期记忆】:{self.summary} 【待归档对话】:{text_block} "
""
get_running_loop
run_in_executor
generate_content
0
0
0
0
"MEMORY_KEEP_COUNT"
save_data
print
"压缩记忆失败:{e}"
passasyncdefgenerate_reply
timezone
'Asia/Shanghai'
"\n"
join
'不要加前缀'
"没人艾特你,自然加入讨论,简短、像群友,可复读/玩梗,直接说话不加称呼,绝对不要输出任何时间戳/日期/时间格式,不要添加自己的名字前缀(如【Gemini】)。"
strip
"用户【{sender_name}】艾特了你,自然接话,绝对不要输出任何时间戳/日期/时间格式,不要添加自己的名字前缀(如【Gemini】)。"
"用户【{sender_name}】提问:{current_question},请回答,绝对不要输出任何时间戳/日期/时间格式,不要添加自己的名字前缀(如【Gemini】)。"
""
" 你是QQ群里的AI伙伴Gemini。 【长期记忆】:{self.summary} 【近期对话】:{context_str} 【任务】:{task_prompt} 【规则】: 1. 问时间时只说当前北京时间(比如'现在是下午3点20分'),但不要输出任何括号/时间戳/数字格式的时间; 2. 问新闻/数据自动联网,风格像群友而非客服; 3. 回复时禁止添加自己的名字或任何标识前缀(如【Gemini】),直接输出回复内容。 "
""
get_running_loop
run_in_executor
generate_content
0
0
0
0
"(暂时无法回复)"
"(回复失败:{str(e)})"
asyncdefcheck_active_intervention
time
"COOLDOWN_SECONDS"
Noneif
"REPEATER_THRESHOLD"
random
"REPEATER_EXEC_PROB"
strip
get_running_loop
run_in_executor
generate_content
"判断'{current_msg}'是否是正常梗(非广告/脏话):是回YES,否回NO"
"YES"
0
0
strip
upper
"gemini"
lower
random
"TRIGGER_PROBABILITY"
get_running_loop
run_in_executor
generate_content
""
" 最近消息:{chr(10).join(self.buffer[-15:])} 当前消息:{current_msg} 没被艾特,是否有必要说话?满足(客观问题无人答/讨论你/有趣的氛围/有趣的话题)回YES,否则NO "
""
"YES"
0
0
strip
upper
generate_reply
""
"群友"
print
"主动介入失败:{e}"
withopen
'wb'
dump
print
"保存记忆失败:{e}"
defload_data
exists
withopen
'rb'
load
GroupMemory
load_data
  1. 创建 bot.py
    • 使用 nano bot.py 创建:
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 # ================= 管理员配置 ================= ADMIN_QQ =1234567# 管理员QQ号(替换成实际的)# ============================================== nonebot.init() driver = nonebot.get_driver() driver.register_adapter(Adapter) atexit.register(save_data)defunix_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")defget_current_bj_time():"""获取当前北京时间字符串"""return datetime.datetime.now(pytz.timezone('Asia/Shanghai')).strftime("%H:%M:%S")defis_at_me(bot: Bot, event: GroupMessageEvent)->bool:"""判断是否@了机器人"""if event.is_tome():returnTruefor seg in event.message:if seg.type=="at"andstr(seg.data.get("qq"))==str(bot.self_id):returnTruereturnFalse# === 单核处理器 === handler = on_message(priority=1, block=True)@handler.handle()asyncdef_(bot: Bot, event: GroupMessageEvent):# ===== 变量初始化 ===== user_id = event.user_id group_id = event.group_id msg = event.get_plaintext().strip() mem = get_memory(group_id)# 获取用户昵称(优先群名片,其次QQ昵称,最后用户ID)try: info =await bot.get_group_member_info(group_id=group_id, user_id=user_id) name = info.get('card')or info.get('nickname')orstr(user_id)except: name ="群友"# ===== 核心:统一转小写判断指令前缀(不区分大小写)===== msg_lower = msg.lower()# 转小写,仅用于指令判断# 提取指令内容(保留原大小写)defget_cmd_content(prefix): prefix_len =len(prefix)# 精确匹配前缀(原大小写)if msg.startswith(prefix):return msg[prefix_len:].strip()# 兼容大小写的前缀匹配elif msg_lower.startswith(prefix.lower()):for i inrange(len(msg)):if msg[i:i+len(prefix)].lower()== prefix.lower():return msg[i+len(prefix):].strip()return""# ===== 1. 管理员专属:修改机器人参数 =====if msg_lower.startswith("gemini 设置 "): cmd_content = get_cmd_content("gemini 设置 ")# 记录原始消息 mem.add_message(name, msg, get_current_bj_time()) save_data() reply_msg =""if user_id != ADMIN_QQ: reply_msg ="❌ 你没有权限执行此操作!仅管理员可用~"else:try:# 分割指令参数 cmd_parts = msg.split(" ",3)iflen(cmd_parts)!=4: reply_msg ="❌ 指令格式错误!\n正确格式:gemini 设置 参数名 值\n示例:gemini 设置 冷却时间 600"else: param_map ={"冷却时间":"COOLDOWN_SECONDS","插嘴概率":"TRIGGER_PROBABILITY","复读阈值":"REPEATER_THRESHOLD","复读概率":"REPEATER_EXEC_PROB","缓存字数上限":"MEMORY_LIMIT_CHARS","保留消息数":"MEMORY_KEEP_COUNT"} param_cn = cmd_parts[2] param_value = cmd_parts[3]if param_cn notin param_map: valid_params ="、".join(param_map.keys()) reply_msg =f"❌ 参数名错误!\n支持的参数:{valid_params}"else: param_en = param_map[param_cn]# 转换参数值类型if param_en in["COOLDOWN_SECONDS","REPEATER_THRESHOLD","MEMORY_LIMIT_CHARS","MEMORY_KEEP_COUNT"]: new_value =int(param_value)else: new_value =float(param_value)# 更新参数 mem.bot_config[param_en]= new_value save_data() reply_msg =f"✅ 【{group_id}群】参数修改成功!\n{param_cn} = {new_value}"except ValueError: reply_msg ="❌ 参数值格式错误!\n整数填数字(如600),概率填小数(如0.02)"except Exception as e: reply_msg =f"❌ 修改失败:{str(e)}"# 记录并回复 mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg)# ===== 2. 全员可用:打卡指令 =====elif msg_lower.startswith("gemini 打卡 "): content = get_cmd_content("gemini 打卡 ")ifnot content: reply_msg ="❌ 打卡内容不能为空哦~比如:「gemini 打卡 篮球」"else:# 执行打卡(user_id转字符串确保唯一性) mem.checkin(str(user_id), name, content)# 生成排行榜 ranking = mem.generate_checkin_ranking() reply_msg =f"✅ {name} 打卡「{content}」成功!\n{ranking}"# 记录并回复 mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg)# ===== 3. 全员可用:删除打卡指令 =====elif msg_lower.startswith("gemini 删除 "): content = get_cmd_content("gemini 删除 ")ifnot content: reply_msg ="❌ 删除内容不能为空哦~比如:「gemini 删除 篮球」"else:# 执行删除 success, result = mem.delete_checkin(str(user_id), content)if success: ranking = mem.generate_checkin_ranking() reply_msg =f"{result}\n{ranking}"else: reply_msg = result # 记录并回复 mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg)# ===== 4. 全员可用:查询排行榜 =====elif msg_lower.strip()=="gemini 排行榜":# 生成排行榜 ranking = mem.generate_checkin_ranking() reply_msg =f"{ranking}"# 记录并回复 mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg)# ===== 5. 管理员专属:管理他人打卡记录(QQ号操作)=====elif msg_lower.startswith("gemini 管理打卡 "):if user_id != ADMIN_QQ: reply_msg ="❌ 你没有权限执行此操作!仅管理员可用~" mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg)# 提取指令内容 cmd_content = get_cmd_content("gemini 管理打卡 ")ifnot cmd_content: reply_msg ="❌ 指令格式错误!\n正确格式:\ngemini 管理打卡 增加 目标QQ号 内容 次数\n示例:gemini 管理打卡 增加 123456 跑步 3\n\ngemini 管理打卡 删除 目标QQ号 内容 次数\n示例:gemini 管理打卡 删除 123456 跑步 1" mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg)# 分割参数 cmd_parts = cmd_content.split()iflen(cmd_parts)!=4: reply_msg ="❌ 指令参数不足!\n正确格式:gemini 管理打卡 增加/删除 目标QQ号 内容 次数" mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg) operation = cmd_parts[0] target_qq = cmd_parts[1] content = cmd_parts[2] count_str = cmd_parts[3]# 校验操作类型if operation notin["增加","删除"]: reply_msg ="❌ 操作类型错误!仅支持「增加」或「删除」" mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg)# 校验QQ号为纯数字ifnot target_qq.isdigit(): reply_msg ="❌ 目标QQ号必须是纯数字!" mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg)# 校验次数为正整数ifnot count_str.isdigit()orint(count_str)<=0: reply_msg ="❌ 次数必须是正整数!" mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg) count =int(count_str)# 获取目标用户昵称(优先群名片,否则用QQ号)try: target_info =await bot.get_group_member_info(group_id=group_id, user_id=target_qq) target_name = target_info.get('card')or target_info.get('nickname')or target_qq except: target_name = target_qq # 非群成员则显示QQ号# 执行管理员操作 op_map ={"增加":"add","删除":"delete"} success, result = mem.admin_checkin( target_user_id=str(target_qq), target_user_name=target_name, content=content, count=count, operation=op_map[operation])# 生成回复(含排行榜)if success: ranking = mem.generate_checkin_ranking() reply_msg =f"{result}\n{ranking}"else: reply_msg = result # 记录并回复 mem.add_message(name, msg, get_current_bj_time()) mem.add_message("Gemini", reply_msg, get_current_bj_time()) save_data()await handler.finish(reply_msg)# ===== 6. 普通消息处理逻辑 =====ifnot msg: msg =" "# 记录普通消息 mem.add_message(name, msg, unix_to_beijing(event.time)) save_data()# 消息压缩逻辑await mem.check_and_compress()# 主动回复/插话逻辑 final_reply =Noneif is_at_me(bot, event): final_reply =await mem.generate_reply(msg, name, is_active_interrupt=False)else: should, content =await mem.check_active_intervention(msg)if should and content: final_reply = content # 发送主动回复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)

为了实现您要求的命令管理,我们需要创建一个系统服务文件。

加载服务

sudo systemctl daemon-reload sudo systemctl enable gemini-bot 

粘贴内容
(注意:ExecStart 指向了虚拟环境中的 python,确保路径准确)

[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 

创建服务文件

sudonano /etc/systemd/system/gemini-bot.service 

🖥️ 第五阶段:NapCat 连接 (WebUI)

【避坑重点】:因为我们用了 host 模式,这里的 WS 地址必须填 127.0.0.1。

  1. 浏览器访问 NapCat
    访问 http://服务器IP:6099/webui
    (如果没有Token,输入 napcat 或查看日志 docker logs napcat)
  2. 扫码登录
    使用手机 QQ 扫码。
  3. 配置 WebSocket (Reverse)
    • 点击左侧 网络配置 -> WebSocket (Reverse)
    • URL 填写:ws://127.0.0.1:8080/onebot/v11/ws
    • 启用:打钩
    • 点击 保存。

重启 NapCat 生效

docker restart napcat 

启动机器人

systemctl start gemini-bot 

🎮 最终:管理命令速查表

恭喜!部署完成。以后您只需要使用以下命令管理您的机器人:

功能命令
启动机器人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 即为成功)

目录

  1. Gemini 全能 QQ 机器人部署手册 (V1.0 Release)
  2. 🟢 第一阶段:基础设施准备
  3. 更新软件源sudoapt update &&sudoapt upgrade -y# 安装 Dockercurl-fsSL https://get.docker.com |bash# 安装 Python3 及虚拟环境工具sudoaptinstall python3-pip python3-venv -y# 创建项目文件夹mkdir-p /root/geminibot cd /root/geminibot
  4. 🔌 第二阶段:部署 QQ 协议端 (NapCat)
  5. 🧠 第三阶段:部署核心代码
  6. 确保在项目目录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
  7. 恢复旧版导入(关键!)import google.generativeai as genai import asyncio import datetime import pytz import pickle import os import random # ================= 核心配置区 ================= APIKEY ="在这里粘贴你的GoogleAPIKey"# 必须替换! DATAFILE ="botmemory.pkl"# ============================================# 旧版兼容写法(0.3.2 支持) genai.configure(apikey=APIKEY) chatmodel = genai.GenerativeModel('gemini-2.5-flash')classGroupMemory:definit(self, groupid): self.groupid = groupid self.summary ="暂无早期历史记录。" self.buffer=[] self.lock = asyncio.Lock()# 异步锁(无法序列化)# 状态追踪 self.lastactivetime =0 self.lastmsgcontent ="" self.repeatcount =0# ===== 每个群独立的参数配置 ===== self.botconfig ={"COOLDOWNSECONDS":0,# 主动说话冷却 (0分钟)"TRIGGERPROBABILITY":0.05,# 闲聊插嘴概率 (5%)"REPEATERTHRESHOLD":2,# 复读检测阈值 (至少2人)"REPEATEREXECPROB":0.1,# 复读执行概率 (10%,防止刷屏)"MEMORYLIMITCHARS":10000,# 缓存字数上限"MEMORYKEEPCOUNT":100# 压缩时保留最近 100 条}# ===== 修复:打卡数据存储(移除lambda,改用普通字典)=====# 结构:{用户ID: {"name": 用户名, "totalstars": 总星星数, "records": {打卡内容: 次数}}} self.checkindata ={}# 普通字典,不再用defaultdict(lambda)# ===== 新增:自定义序列化/反序列化方法(跳过Lock)=====defgetstate(self):# 序列化时,排除lock对象,返回其他需要保存的属性 state = self.dict.copy()del state['lock']# 移除无法序列化的Lockreturn state defsetstate(self, state):# 反序列化时,恢复所有属性,并重新初始化Lock self.dict.update(state) self.lock = asyncio.Lock()# 重新创建Lock对象# ========== 修复:打卡功能方法(手动初始化默认值)==========defcheckin(self, userid, username, content):"""执行打卡:增加记录+总星星"""# 手动初始化用户数据(无lambda,兼容pickle)if userid notin self.checkindata: self.checkindata[userid]={"name":"","totalstars":0,"records":{}# 普通字典存打卡内容} userdata = self.checkindata[userid] userdata["name"]= username # 更新最新昵称# 手动初始化打卡内容if content notin userdata["records"]: userdata["records"][content]=0 userdata["records"][content]+=1 userdata["totalstars"]+=1# 1次打卡=1颗星 savedata()# 立即持久化defdeletecheckin(self, userid, content):"""删除打卡:减少记录+总星星(返回:是否成功, 提示语)"""if userid notin self.checkindata:returnFalse,"你还没有任何打卡记录哦~" userdata = self.checkindata[userid]if content notin userdata["records"]or userdata["records"][content]==0:returnFalse,f"你没有「{content}」的打卡记录~"# 减少次数 userdata["records"][content]-=1 userdata["totalstars"]-=1# 内容次数为0则移除该记录if userdata["records"][content]==0:del userdata["records"][content]# 无任何记录则移除用户ifnot userdata["records"]:del self.checkindata[userid] savedata()returnTrue,f"已删除「{content}」的1次打卡记录~"# ========== 管理员管理他人打卡记录的方法 ==========defadmincheckin(self, targetuserid, targetusername, content, count, operation):""" 管理员操作他人打卡记录 operation: "add"(增加) / "delete"(删除) 返回:是否成功, 提示语 """# 1. 初始化目标用户数据if targetuserid notin self.checkindata: self.checkindata[targetuserid]={"name": targetusername,"totalstars":0,"records":{}} userdata = self.checkindata[targetuserid] userdata["name"]= targetusername # 更新目标用户昵称# 2. 初始化打卡内容if content notin userdata["records"]: userdata["records"][content]=0# 3. 执行增加/删除操作if operation =="add": userdata["records"][content]+= count userdata["totalstars"]+= count savedata()returnTrue,f"已为用户【{targetusername}】增加「{content}」打卡记录 {count} 次~"elif operation =="delete":if userdata["records"][content]< count:returnFalse,f"用户【{targetusername}】的「{content}」打卡记录不足 {count} 次!" userdata["records"][content]-= count userdata["totalstars"]-= count # 内容次数为0则移除该记录if userdata["records"][content]==0:del userdata["records"][content]# 无任何记录则移除用户ifnot userdata["records"]:del self.checkindata[targetuserid] savedata()returnTrue,f"已为用户【{targetusername}】删除「{content}」打卡记录 {count} 次~"else:returnFalse,"操作类型错误!仅支持「增加」或「删除」"defcalculatemedals(self, stars):"""计算星星对应的勋章:4星=1月,4月=1日,4日=1皇冠""" crown = stars //(444)# 皇冠:64星 stars %=(444) sun = stars //(44)# 太阳:16星 stars %=(44) moon = stars //4# 月亮:4星 star = stars %4# 剩余星星 medals =""if crown >0: medals +="👑" crown if sun >0: medals +="☀️" sun if moon >0: medals +="🌙" moon if star >0or(crown+sun+moon+star ==0): medals +="⭐" star return medals defgeneratecheckinranking(self):"""生成打卡排行榜(按总星星降序,子项也显示勋章)"""ifnot self.checkindata:return"当前还没有打卡记录哦~快叫群友一起打卡吧!"# 按总星星排序(兼容普通字典) sortedusers =sorted( self.checkindata.values(), key=lambda x: x["totalstars"], reverse=True)# 格式化输出 ranking ="🏆 打卡记录排行榜 🏆\n"for idx, user inenumerate(sortedusers,1): totalmedals = self.calculatemedals(user["totalstars"]) ranking +=f"{idx}. {user['name']}{totalmedals}\n"# 子项显示对应的勋章for content, count in user["records"].items(): contentmedals = self.calculatemedals(count) ranking +=f" · {content}{contentmedals}\n"return ranking # 原有方法(不变)defaddmessage(self, name, msg, timestr): entry =f"[{timestr}] 【{name}】: {msg}" self.buffer.append(entry) cleanmsg = msg.strip()if cleanmsg == self.lastmsgcontent and cleanmsg: self.repeatcount +=1else: self.lastmsgcontent = cleanmsg self.repeatcount =1defestimatelength(self):returnsum(len(m)for m in self.buffer)asyncdefcheckandcompress(self):asyncwith self.lock:if self.estimatelength()> self.botconfig["MEMORYLIMITCHARS"]andlen(self.buffer)> self.botconfig["MEMORYKEEPCOUNT"]: tocompress = self.buffer[:-self.botconfig["MEMORYKEEPCOUNT"]] textblock ="\n".join(tocompress) prompt =f""" 你是记忆整理员,更新长期记忆摘要(800字内): 【长期记忆】:{self.summary} 【待归档对话】:{textblock} """try: loop = asyncio.getrunningloop() resp =await loop.runinexecutor(None,lambda: chatmodel.generatecontent(prompt))if resp.candidates[0].content.parts[0].text: self.summary = resp.candidates[0].content.parts[0].text self.buffer= self.buffer[-self.botconfig["MEMORYKEEPCOUNT"]:] savedata()except Exception as e:print(f"压缩记忆失败:{e}")passasyncdefgeneratereply(self, currentquestion, sendername, isactiveinterrupt=False): tz = pytz.timezone('Asia/Shanghai') contextstr ="\n".join(self.buffer)# 构建回复提示词(补充“不要加前缀”的限制)if isactiveinterrupt: taskprompt ="没人艾特你,自然加入讨论,简短、像群友,可复读/玩梗,直接说话不加称呼,绝对不要输出任何时间戳/日期/时间格式,不要添加自己的名字前缀(如【Gemini】)。"else:ifnot currentquestion.strip(): taskprompt =f"用户【{sendername}】艾特了你,自然接话,绝对不要输出任何时间戳/日期/时间格式,不要添加自己的名字前缀(如【Gemini】)。"else: taskprompt =f"用户【{sendername}】提问:{currentquestion},请回答,绝对不要输出任何时间戳/日期/时间格式,不要添加自己的名字前缀(如【Gemini】)。" finalprompt =f""" 你是QQ群里的AI伙伴Gemini。 【长期记忆】:{self.summary} 【近期对话】:{contextstr} 【任务】:{taskprompt} 【规则】: 1. 问时间时只说当前北京时间(比如“现在是下午3点20分”),但不要输出任何括号/时间戳/数字格式的时间; 2. 问新闻/数据自动联网,风格像群友而非客服; 3. 回复时禁止添加自己的名字或任何标识前缀(如【Gemini】),直接输出回复内容。 """try: loop = asyncio.getrunningloop() resp =await loop.runinexecutor(None,lambda: chatmodel.generatecontent(finalprompt))return resp.candidates[0].content.parts[0].text if resp.candidates[0].content.parts[0].text else"(暂时无法回复)"except Exception as e:returnf"(回复失败:{str(e)})"asyncdefcheckactiveintervention(self, currentmsg):import time now = time.time()if now - self.lastactivetime < self.botconfig["COOLDOWNSECONDS"]:returnFalse,Noneif(self.repeatcount >= self.botconfig["REPEATERTHRESHOLD"]and random.random()< self.botconfig["REPEATEREXECPROB"]and currentmsg.strip()):try: loop = asyncio.getrunningloop() checkresp =await loop.runinexecutor(None,lambda: chatmodel.generatecontent(f"判断'{currentmsg}'是否是正常梗(非广告/脏话):是回YES,否回NO"))if"YES"in checkresp.candidates[0].content.parts[0].text.strip().upper(): self.lastactivetime = now returnTrue, currentmsg except:passreturnFalse,Noneif"gemini"notin currentmsg.lower()and random.random()> self.botconfig["TRIGGERPROBABILITY"]:returnFalse,Nonetry: loop = asyncio.getrunningloop() decisionresp =await loop.runinexecutor(None,lambda: chatmodel.generatecontent(f""" 最近消息:{chr(10).join(self.buffer[-15:])} 当前消息:{currentmsg} 没被艾特,是否有必要说话?满足(客观问题无人答/讨论你/有趣的氛围/有趣的话题)回YES,否则NO """))if"YES"in decisionresp.candidates[0].content.parts[0].text.strip().upper(): reply =await self.generatereply("","群友", isactiveinterrupt=True) self.lastactivetime = now returnTrue, reply except Exception as e:print(f"主动介入失败:{e}")passreturnFalse,None# --- 记忆持久化(不变)--- memories ={}defsavedata():try:withopen(DATAFILE,'wb')as f: pickle.dump(memories, f)except Exception as e:print(f"保存记忆失败:{e}")defloaddata():global memories if os.path.exists(DATAFILE):try:withopen(DATAFILE,'rb')as f: memories = pickle.load(f)except: memories ={}defgetmemory(groupid):if groupid notin memories: memories[groupid]= GroupMemory(groupid)return memories[groupid] load_data()
  8. ⚙️ 第四阶段:配置系统服务 (Systemd)
  9. 🖥️ 第五阶段:NapCat 连接 (WebUI)
  10. 🎮 最终:管理命令速查表
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 32 个实用渗透测试技巧收集
  • C++ 搜索引擎核心模块:文件读取与分词工具类实现
  • cJSON 1.7.19 源码深度剖析:数据结构、解析流程与注释实践
  • 数据结构详解:选择排序原理与 Java 实现
  • 智慧企业为何选择大模型重塑客服体系
  • 基于 Coze 平台从零搭建企业级 AI 客服机器人实战指南
  • 13 篇大模型前沿论文精选:推理、视频与医疗应用
  • Qt Creator 配置 GitHub Copilot 插件实战指南
  • 智能桌面机器人快速搭建指南:ElectronBot 项目实践
  • RAFT 光流估计算法深度解析与实践指南
  • Python 爬虫抓取企业年报与财务报告技术实现
  • 昇腾 910B 部署 Llama-2-7b 大模型深度测评与方案
  • C++ 备忘录模式:一个可撤销状态的实现与权衡
  • Webmin CVE-2019-15107 漏洞深度解析:从后门到修复
  • 网络安全基础教程:概念、加密与攻击防护
  • Linux 进程间通信进阶:管道与共享内存详解
  • 基于 Kaggle 免费环境体验 Stable Diffusion AI 绘画入门
  • 前后端分离与不分离架构对比分析
  • RunningHub:基于开源生态的图形音视频 AIGC 平台解析
  • Spring 嵌入式内存数据库选型:H2、Derby 与 HSQL 对比

相关免费在线工具

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online