maxkb知识库 微信机器人对接
#!/usr/bin/env python # -*- coding:utf-8 -*- # Author : zhibo.wang # E-mail : gm.zhibo.wang@gmail.com # Date : # Desc : 每个群对应一个会话ID from common.log import logger from plugins import * from bridge.context import ContextType from bridge.reply import Reply, ReplyType from channel.chat_message import ChatMessage from channel.wechatnt.nt_run import wechatnt try: import json import time import plugins import datetime import requests from fake_useragent import UserAgent from .xinuo_utils import Util, DailyNumberLimiter import uuid import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) except Exception as e: logger.error(f"[Xinuo] import error: {e}") def get_with_retry(get_func, max_retries=5, delay=5): retries = 0 result = None while retries < max_retries: result = get_func() logger.warning(f"获取数据,第{retries + 1}次······") if result: break retries += 1 time.sleep(delay) # 等待一段时间后重试 return result @plugins.register( name="Xinuo", # 插件的名称 desire_priority=666, # 插件的优先级 数据越大优先级越高 hidden=False, # 插件是否隐藏 desc="技术客服", # 插件的描述 version="0.0.5", # 插件的版本号 author="gm.zhibo.wang@gmail.com", # 插件的作者 ) class Xinuo(Plugin): def __init__(self): super().__init__() tag = "xinuo 初始化" self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context try: self.conf = super().load_config() self.kb_base_url = self.conf.get("kb_base_url", "") self.kb_token = self.conf.get("kb_token", "") self.kb_profile_id = self.conf.get("kb_profile_id", "") self.kb_group_name_chat_ids = self.conf.get("kb_group_name_chat_ids", {}) logger.info("[Xinuo] inited") except Exception as e: log_msg = f"{tag}: init failed error: {e}" logger.error(log_msg) def on_handle_context(self, e_context: EventContext, retry_count: int = 0): """ TEXT = 1 # 文本消息 VOICE = 2 # 音频消息 IMAGE = 3 # 图片消息 FILE = 4 # 文件信息 VIDEO = 5 # 视频信息 IMAGE_CREATE = 10 # 创建图片命令 ACCEPT_FRIEND = 19 # 同意好友请求 JOIN_GROUP = 20 # 加入群聊 PATPAT = 21 # 拍了拍 FUNCTION = 22 # 函数调用 EXIT_GROUP = 23 # 退出 """ if e_context["context"].type not in [ ContextType.TEXT, ContextType.FILE, ContextType.PATPAT, ContextType.JOIN_GROUP, ContextType.EXIT_GROUP, ]: return e_msg: ChatMessage = e_context["context"]["msg"] context = e_context["context"] cmsg = context["msg"] isgroup = context.get("isgroup", False) user = context["receiver"] content = context.content.lower().strip() session_id = context["session_id"] # `session_id`: 会话ID(一般是发送触发bot消息的用户ID if not Util.is_admin(e_context): is_admin = False else: is_admin = True if isgroup: group_name = cmsg.other_user_nickname group_id = cmsg.other_user_id actual_user_nickname = cmsg.actual_user_nickname from_user_nickname = cmsg.from_user_nickname else: group_name = "" group_id = "" actual_user_nickname = "" from_user_nickname = cmsg.from_user_nickname logger.info(f"""[xinuo] on_handle_context, session_id: {session_id}, isgroup: {isgroup}, is_admin: {is_admin}, group_name: {group_name}, group_id: {group_id}, actual_user_nickname: {actual_user_nickname}, from_user_nickname: {from_user_nickname}, content: {content} """.replace(" ", "").replace("\n", "")) if e_context["context"].type == ContextType.TEXT: if content == "更新群成员": tag = "更新群成员" gpt_text = content.strip() logger.info(f"{tag}: {gpt_text}") msg = self.update_rooms(tag, group_name) reply = self.create_reply(ReplyType.TEXT, tag, msg) e_context["reply"] = reply e_context.action = EventAction.BREAK_PASS elif "@所有人" not in content: # 默认使用 科技智能AI tag = "技术客服" gpt_text = content.strip() logger.info(f"{tag}: {gpt_text}") reply_1 = Reply( ReplyType.TEXT, f"[🤖] {tag}\n🎉正在为您转接客服,请稍候...\n----------------------\n[🤖]") channel = e_context["channel"] channel.send(reply_1, context) msg = self.fun_yunshangkeji_bot(gpt_text, group_name, tag) reply = self.create_reply(ReplyType.TEXT, tag, msg) e_context["reply"] = reply e_context.action = EventAction.BREAK_PASS else: e_context.action = EventAction.BREAK_PASS elif e_context["context"].type == ContextType.PATPAT: logger.info(f"拍一拍 session_id: {session_id}, content: {content}") if "拍了拍我" in content: tag = "拍一拍" content = "有什么可以帮助您吗?" reply = self.create_reply(ReplyType.TEXT, tag, content) e_context["reply"] = reply e_context.action = EventAction.BREAK_PASS else: return def create_reply(self, reply_type, tag, content): reply = Reply() reply.type = reply_type content = f"{tag}\n{content}" reply.content = content return reply def get_help_text(self, verbose=False, **kwargs): help_text = "发送关键词执行对应操作\n" if not verbose: return help_text help_text += "输入 '内容', 默认使用技术客服进行回答\n" return help_text def get_timestamp(self, n=13): # 获取时间戳 返回13位或者10位时间戳 if n == 13: return str(int(time.time()*1000)) else: return str(int(time.time())) def random_user_agent(self): # 随机获取user-agent U = UserAgent() return U.random def edit_config_json(self, key, value): # 修改xinuo配置文件信息 curdir = os.path.dirname(__file__) config_path = os.path.join(curdir, "config.json") with open(config_path, 'r') as file: data = json.load(file) data[key] = value with open(config_path, 'w') as file: json.dump(data, file, indent=4) logger.info(f"修改配置文件: key {key}, value: {value}") def update_rooms(self, tag, group_name): # tag = "更新群成员" msg = f"{tag}: 服务器睡着了,请稍后再试" try: rooms = get_with_retry(wechatnt.get_rooms) if rooms: logger.info(f"{tag}: {rooms}") for room in rooms: if group_name == room.get("nickname"): room_status = True logger.info(f"找到对应的群聊: {room}") wxid = room.get("wxid") room_members = wechatnt.get_room_members(wxid) if room_members: logger.info(f"room_members: {room_members}") room_members_total = room_members.get("total") msg = f"群成员总数: {room_members_total}" except Exception as e: logger.error(f"{tag}: 服务器内部错误 {e}") return msg def get_kb_chat_id(self): # chat_id tag = "获取对话ID" msg = f"{tag}: 服务器睡着了,请稍后再试" chat_id = None try: url = f'{self.kb_base_url}/api/application/{self.kb_profile_id}/chat/open' params = None headers = { 'Accept': 'application/json', 'Accept-Language': 'zh-CN,zh;q=0.9', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', 'Content-Type': 'application/json', 'AUTHORIZATION': self.kb_token } response = requests.request( "GET", url, headers=headers, params=params, timeout=30 ) print(f"{tag}: {response.text}") r_code = response.status_code if r_code == 200: res_json = response.json() if res_json.get("code") == 200: chat_id = res_json.get("data") else: message = res_json.get("message") log_msg = f"{tag}: response message:{message}" logger.info(log_msg) else: r_code = response.status_code log_msg = f"{tag}: response status_code:{r_code}" logger.info(log_msg) except Exception as e: logger.error(f"{tag}: 服务器内部错误 {e}") return msg, chat_id def get_group_name_chat_id(self, group_name): group_name_chat_id = self.kb_group_name_chat_ids.get(group_name) if group_name_chat_id is None: _, chat_id = self.get_kb_chat_id() if chat_id: self.kb_group_name_chat_ids[group_name] = chat_id self.edit_config_json( "kb_group_name_chat_ids", self.kb_group_name_chat_ids ) group_name_chat_id = chat_id return group_name_chat_id def fun_yunshangkeji_bot(self, gpt_text, group_name, tag): # kb 知识库 msg = f"{tag}: 服务器睡着了,请稍后再试" try: kb_chat_id = self.get_group_name_chat_id(group_name) url = f'{self.kb_base_url}/api/application/chat_message/{kb_chat_id}' params = None payload = json.dumps({ "message": gpt_text, "re_chat": False, "stream": False }) headers = { 'Accept': 'application/json', 'Accept-Language': 'zh-CN,zh;q=0.9', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', 'Content-Type': 'application/json', 'AUTHORIZATION': self.kb_token } response = requests.request( "POST", url, headers=headers, params=params, data=payload, timeout=30 ) r_code = response.status_code if r_code == 200: res_json = response.json() if res_json.get("code") == 200: msg = res_json.get("data").get("content") else: message = res_json.get("message") log_msg = f"{tag}: response message:{message}" logger.info(log_msg) else: r_code = response.status_code log_msg = f"{tag}: response status_code:{r_code}" logger.info(log_msg) except Exception as e: logger.error(f"{tag}: 服务器内部错误 {e}") return msg
config.json { "kb_base_url": "http://192.168.20.59:8080", "kb_token": "", "kb_profile_id": "", "kb_group_name_chat_ids": {} }
cat xinuo_utils.py 10:40:59 ☁ master ☀ #!/usr/bin/env python # -*- coding:utf-8 -*- # Author: zhibo.wang # E-mail: gm.zhibo.wang@gmail.com # Date : # Desc : import pytz from collections import defaultdict import datetime from config import global_config from bridge.reply import Reply, ReplyType from plugins.event import EventContext, EventAction class Util: @staticmethod def is_admin(e_context: EventContext) -> bool: """ 判断消息是否由管理员用户发送 :param e_context: 消息上下文 :return: True: 是, False: 否 """ context = e_context["context"] if context["isgroup"]: actual_user_id = context.kwargs.get("msg").actual_user_id for admin_user in global_config["admin_users"]: if actual_user_id and actual_user_id in admin_user: return True return False else: return context["receiver"] in global_config["admin_users"] @staticmethod def set_reply_text(content: str, e_context: EventContext, level: ReplyType = ReplyType.ERROR): reply = Reply(level, content) e_context["reply"] = reply e_context.action = EventAction.BREAK_PASS class DailyNumberLimiter: tz = pytz.timezone('Asia/Shanghai') def __init__(self, max_num): self.today = -1 self.count = defaultdict(int) self.max = max_num def check(self, key) -> bool: now = datetime.datetime.now(self.tz) day = (now - datetime.timedelta(hours=5)).day if day != self.today: self.today = day self.count.clear() return bool(self.count[key] < self.max) def get_num(self, key): return self.count[key] def increase(self, key, num=1): self.count[key] += num def reset(self, key): self.count[key] = 0