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

  

posted @ 2024-07-19 10:38  🐳.城南  阅读(8)  评论(0编辑  收藏  举报