diff --git a/app.py b/app.py index 8dc1e970..ba2ab265 100644 --- a/app.py +++ b/app.py @@ -231,7 +231,7 @@ def _clear_singleton_cache(channel_name: str): "wechatmp": "channel.wechatmp.wechatmp_channel.WechatMPChannel", "wechatmp_service": "channel.wechatmp.wechatmp_channel.WechatMPChannel", "wechatcom_app": "channel.wechatcom.wechatcomapp_channel.WechatComAppChannel", - const.WECHATCOM_KF: "channel.wechatcom_kf.wechatcom_kf_channel.WechatComKfChannel", + const.WECHAT_KF: "channel.wechat_kf.wechat_kf_channel.WechatKfChannel", const.FEISHU: "channel.feishu.feishu_channel.FeiShuChanel", const.DINGTALK: "channel.dingtalk.dingtalk_channel.DingTalkChanel", const.WECOM_BOT: "channel.wecom_bot.wecom_bot_channel.WecomBotChannel", diff --git a/channel/channel_factory.py b/channel/channel_factory.py index 9f35e3fc..10000226 100644 --- a/channel/channel_factory.py +++ b/channel/channel_factory.py @@ -27,9 +27,9 @@ def create_channel(channel_type) -> Channel: elif channel_type == "wechatcom_app": from channel.wechatcom.wechatcomapp_channel import WechatComAppChannel ch = WechatComAppChannel() - elif channel_type == const.WECHATCOM_KF: - from channel.wechatcom_kf.wechatcom_kf_channel import WechatComKfChannel - ch = WechatComKfChannel() + elif channel_type == const.WECHAT_KF: + from channel.wechat_kf.wechat_kf_channel import WechatKfChannel + ch = WechatKfChannel() elif channel_type == const.FEISHU: from channel.feishu.feishu_channel import FeiShuChanel ch = FeiShuChanel() diff --git a/channel/web/web_channel.py b/channel/web/web_channel.py index 766c7126..53f26c46 100644 --- a/channel/web/web_channel.py +++ b/channel/web/web_channel.py @@ -713,7 +713,7 @@ class WebChannel(ChatChannel): logger.info("[WebChannel] 5. dingtalk - 钉钉") logger.info("[WebChannel] 6. wecom_bot - 企微智能机器人") logger.info("[WebChannel] 7. wechatcom_app - 企微自建应用") - logger.info("[WebChannel] 8. wechatcom_kf - 微信客服") + logger.info("[WebChannel] 8. wechat_kf - 微信客服") logger.info("[WebChannel] 9. wechatmp - 个人公众号") logger.info("[WebChannel] 10. wechatmp_service - 企业公众号") logger.info("[WebChannel] ✅ Web控制台已运行") @@ -1272,17 +1272,16 @@ class ChannelsHandler: {"key": "wechatcomapp_port", "label": "Port", "type": "number", "default": 9898}, ], }), - ("wechatcom_kf", { + ("wechat_kf", { "label": {"zh": "微信客服", "en": "WeCom Customer Service"}, "icon": "fa-headset", "color": "emerald", "fields": [ - # wechatcom_corp_id is shared with wechatcom_app — same key, same value. - {"key": "wechatcom_corp_id", "label": "Corp ID", "type": "text"}, - {"key": "wechatcom_kf_secret", "label": "Secret", "type": "secret"}, - {"key": "wechatcom_kf_token", "label": "Token", "type": "secret"}, - {"key": "wechatcom_kf_aes_key", "label": "AES Key", "type": "secret"}, - {"key": "wechatcom_kf_port", "label": "Port", "type": "number", "default": 9888}, + {"key": "wechat_kf_corp_id", "label": "Corp ID", "type": "text"}, + {"key": "wechat_kf_secret", "label": "Secret", "type": "secret"}, + {"key": "wechat_kf_token", "label": "Token", "type": "secret"}, + {"key": "wechat_kf_aes_key", "label": "AES Key", "type": "secret"}, + {"key": "wechat_kf_port", "label": "Port", "type": "number", "default": 9888}, ], }), ("wechatmp", { diff --git a/channel/wechatcom_kf/README.md b/channel/wechat_kf/README.md similarity index 80% rename from channel/wechatcom_kf/README.md rename to channel/wechat_kf/README.md index 933e6981..c7352f70 100644 --- a/channel/wechatcom_kf/README.md +++ b/channel/wechat_kf/README.md @@ -32,10 +32,10 @@ | 字段 | 来源 | 对应 CoW 配置项 | |---|---|---| -| 企业ID(CorpId) | 「我的企业」最下方 | `wechatcom_corp_id` | -| Secret | 进入应用详情 → 点击「查看」(会推送到管理员手机端,在手机上查看) | `wechatcom_kf_secret` | -| Token | 应用「接收消息 → 设置API接收」 | `wechatcom_kf_token` | -| EncodingAESKey | 应用「接收消息 → 设置API接收」 | `wechatcom_kf_aes_key` | +| 企业ID(CorpId) | 「我的企业」最下方 | `wechat_kf_corp_id` | +| Secret | 进入应用详情 → 点击「查看」(会推送到管理员手机端,在手机上查看) | `wechat_kf_secret` | +| Token | 应用「接收消息 → 设置API接收」 | `wechat_kf_token` | +| EncodingAESKey | 应用「接收消息 → 设置API接收」 | `wechat_kf_aes_key` | > AgentId 在本通道**不需要**(消息发送走的是 `cgi-bin/kf/send_msg`,不依赖 agent_id)。 @@ -63,25 +63,25 @@ ```json { - "channel_type": "wechatcom_kf", + "channel_type": "wechat_kf", - "wechatcom_corp_id": "ww1234567890abcdef", - "wechatcom_kf_secret": "<企微应用的 Secret>", - "wechatcom_kf_token": "<接收消息 Token>", - "wechatcom_kf_aes_key": "", - "wechatcom_kf_port": 9888 + "wechat_kf_corp_id": "ww1234567890abcdef", + "wechat_kf_secret": "<企微应用的 Secret>", + "wechat_kf_token": "<接收消息 Token>", + "wechat_kf_aes_key": "", + "wechat_kf_port": 9888 } ``` | 字段 | 说明 | |---|---| -| `wechatcom_corp_id` | 企业 ID,可与 `wechatcom_app` 共用 | -| `wechatcom_kf_secret` | **绑定到微信客服**的那个企微自建应用的 Secret | -| `wechatcom_kf_token` | 该应用「接收消息」配置的 Token | -| `wechatcom_kf_aes_key` | 该应用「接收消息」配置的 EncodingAESKey | -| `wechatcom_kf_port` | 监听端口,默认 `9888` | +| `wechat_kf_corp_id` | 企业 ID | +| `wechat_kf_secret` | **绑定到微信客服**的那个企微自建应用的 Secret | +| `wechat_kf_token` | 该应用「接收消息」配置的 Token | +| `wechat_kf_aes_key` | 该应用「接收消息」配置的 EncodingAESKey | +| `wechat_kf_port` | 监听端口,默认 `9888` | -也支持环境变量:`WECHATCOM_CORP_ID` / `WECHATCOM_KF_SECRET` / `WECHATCOM_KF_TOKEN` / `WECHATCOM_KF_AES_KEY`。 +也支持环境变量:`WECHAT_KF_CORP_ID` / `WECHAT_KF_SECRET` / `WECHAT_KF_TOKEN` / `WECHAT_KF_AES_KEY`。 ## 四、运行 @@ -92,8 +92,8 @@ python app.py 启动后日志里会看到: ``` -[wechatcom_kf] WeCom customer-service channel started -[wechatcom_kf] Listening on http://0.0.0.0:9888/wxkf/ +[wechat_kf] WeCom customer-service channel started +[wechat_kf] Listening on http://0.0.0.0:9888/wxkf/ ``` 回到企微后台「设置API接收」点击保存——会触发 `GET /wxkf/?...&echostr=...`,CoW 通过 `crypto.check_signature` 校验后返回明文 `echostr`,验证成功。 diff --git a/channel/wechatcom_kf/wechatcom_kf_channel.py b/channel/wechat_kf/wechat_kf_channel.py similarity index 80% rename from channel/wechatcom_kf/wechatcom_kf_channel.py rename to channel/wechat_kf/wechat_kf_channel.py index 7a72728f..7b8000f3 100644 --- a/channel/wechatcom_kf/wechatcom_kf_channel.py +++ b/channel/wechat_kf/wechat_kf_channel.py @@ -8,7 +8,7 @@ Differences from `channel/wechatcom/` (企微自建应用): member `userid`. 3. Inbound flow: callback only delivers an event token, the actual message bodies must be pulled via `cgi-bin/kf/sync_msg` with a - persistent cursor. See `wechatcom_kf_cursor_store.py`. + persistent cursor. See `wechat_kf_cursor_store.py`. 4. Outbound flow: messages are sent via `cgi-bin/kf/send_msg` (each request must specify both `touser` and `open_kfid`); wechatpy has no native helper, so we call the HTTP endpoint directly. @@ -30,8 +30,8 @@ from wechatpy.exceptions import InvalidSignatureException, WeChatClientException from bridge.context import Context from bridge.reply import Reply, ReplyType from channel.chat_channel import ChatChannel -from channel.wechatcom_kf.wechatcom_kf_cursor_store import CursorStore -from channel.wechatcom_kf.wechatcom_kf_message import WechatComKfMessage +from channel.wechat_kf.wechat_kf_cursor_store import CursorStore +from channel.wechat_kf.wechat_kf_message import WechatKfMessage from common.log import logger from common.singleton import singleton from common.utils import ( @@ -46,7 +46,7 @@ try: from voice.audio_convert import any_to_amr, split_audio except ImportError as e: # voice features optional logger.debug( - "[wechatcom_kf] import voice.audio_convert failed, voice will be disabled: {}".format(e) + "[wechat_kf] import voice.audio_convert failed, voice will be disabled: {}".format(e) ) MAX_UTF8_LEN = 2048 @@ -55,18 +55,18 @@ SYNC_MSG_LIMIT = 1000 @singleton -class WechatComKfChannel(ChatChannel): +class WechatKfChannel(ChatChannel): NOT_SUPPORT_REPLYTYPE = [] def __init__(self): super().__init__() - self.corp_id = conf().get("wechatcom_corp_id") - self.secret = conf().get("wechatcom_kf_secret") - self.token = conf().get("wechatcom_kf_token") - self.aes_key = conf().get("wechatcom_kf_aes_key") + self.corp_id = conf().get("wechat_kf_corp_id") + self.secret = conf().get("wechat_kf_secret") + self.token = conf().get("wechat_kf_token") + self.aes_key = conf().get("wechat_kf_aes_key") self._http_server = None logger.info( - "[wechatcom_kf] Initializing WeCom customer-service channel, corp_id: {}".format( + "[wechat_kf] Initializing WeCom customer-service channel, corp_id: {}".format( self.corp_id ) ) @@ -81,18 +81,18 @@ class WechatComKfChannel(ChatChannel): # Cursor file is an internal implementation detail — fixed under # the project's `tmp/` dir, not exposed as a user-facing config. - cursor_path = os.path.join("tmp", "wechatcom_kf_cursors.json") + cursor_path = os.path.join("tmp", "wechat_kf_cursors.json") self.cursor_store = CursorStore(cursor_path) # ------------------------------------------------------------------ # Lifecycle # ------------------------------------------------------------------ def startup(self): - urls = ("/wxkf/?", "channel.wechatcom_kf.wechatcom_kf_channel.Query") + urls = ("/wxkf/?", "channel.wechat_kf.wechat_kf_channel.Query") app = web.application(urls, globals(), autoreload=False) - port = conf().get("wechatcom_kf_port", 9888) - logger.info("[wechatcom_kf] WeCom customer-service channel started") - logger.info("[wechatcom_kf] Listening on http://0.0.0.0:{}/wxkf/".format(port)) + port = conf().get("wechat_kf_port", 9888) + logger.info("[wechat_kf] WeCom customer-service channel started") + logger.info("[wechat_kf] Listening on http://0.0.0.0:{}/wxkf/".format(port)) func = web.httpserver.StaticMiddleware(app.wsgifunc()) func = web.httpserver.LogMiddleware(func) server = web.httpserver.WSGIServer(("0.0.0.0", port), func) @@ -106,9 +106,9 @@ class WechatComKfChannel(ChatChannel): if self._http_server: try: self._http_server.stop() - logger.info("[wechatcom_kf] HTTP server stopped") + logger.info("[wechat_kf] HTTP server stopped") except Exception as e: - logger.warning(f"[wechatcom_kf] Error stopping HTTP server: {e}") + logger.warning(f"[wechat_kf] Error stopping HTTP server: {e}") self._http_server = None # ------------------------------------------------------------------ @@ -122,7 +122,7 @@ class WechatComKfChannel(ChatChannel): if not external_userid or not open_kfid: logger.error( - "[wechatcom_kf] missing external_userid or open_kfid, cannot send: " + "[wechat_kf] missing external_userid or open_kfid, cannot send: " f"external_userid={external_userid}, open_kfid={open_kfid}" ) return @@ -132,13 +132,13 @@ class WechatComKfChannel(ChatChannel): texts = split_string_by_utf8_length(reply_text, MAX_UTF8_LEN) if len(texts) > 1: logger.info( - "[wechatcom_kf] text too long, split into {} parts".format(len(texts)) + "[wechat_kf] text too long, split into {} parts".format(len(texts)) ) for i, text in enumerate(texts): self._send_text(external_userid, open_kfid, text) if i != len(texts) - 1: time.sleep(0.5) - logger.info("[wechatcom_kf] Do send text to {}: {}".format(receiver, reply_text)) + logger.info("[wechat_kf] Do send text to {}: {}".format(receiver, reply_text)) elif reply.type == ReplyType.VOICE: file_path = reply.content @@ -148,7 +148,7 @@ class WechatComKfChannel(ChatChannel): duration, files = split_audio(amr_file, 60 * 1000) if len(files) > 1: logger.info( - "[wechatcom_kf] voice too long {}s > 60s, split into {} parts".format( + "[wechat_kf] voice too long {}s > 60s, split into {} parts".format( duration / 1000.0, len(files) ) ) @@ -156,14 +156,14 @@ class WechatComKfChannel(ChatChannel): for path in files: with open(path, "rb") as f: response = self.client.media.upload("voice", f) - logger.debug("[wechatcom_kf] upload voice response: {}".format(response)) + logger.debug("[wechat_kf] upload voice response: {}".format(response)) media_ids.append(response["media_id"]) except ImportError as e: - logger.error("[wechatcom_kf] voice conversion failed: {}".format(e)) - logger.error("[wechatcom_kf] please install pydub: pip install pydub") + logger.error("[wechat_kf] voice conversion failed: {}".format(e)) + logger.error("[wechat_kf] please install pydub: pip install pydub") return except WeChatClientException as e: - logger.error("[wechatcom_kf] upload voice failed: {}".format(e)) + logger.error("[wechat_kf] upload voice failed: {}".format(e)) return try: @@ -176,7 +176,7 @@ class WechatComKfChannel(ChatChannel): for media_id in media_ids: self._send_voice(external_userid, open_kfid, media_id) time.sleep(1) - logger.info("[wechatcom_kf] sendVoice={}, receiver={}".format(reply.content, receiver)) + logger.info("[wechat_kf] sendVoice={}, receiver={}".format(reply.content, receiver)) elif reply.type == ReplyType.IMAGE_URL: img_url = reply.content @@ -186,31 +186,31 @@ class WechatComKfChannel(ChatChannel): image_storage.write(block) sz = fsize(image_storage) if sz >= 10 * 1024 * 1024: - logger.info("[wechatcom_kf] image too large, compressing, sz={}".format(sz)) + logger.info("[wechat_kf] image too large, compressing, sz={}".format(sz)) image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1) image_storage.seek(0) try: response = self.client.media.upload("image", image_storage) except WeChatClientException as e: - logger.error("[wechatcom_kf] upload image failed: {}".format(e)) + logger.error("[wechat_kf] upload image failed: {}".format(e)) return self._send_image(external_userid, open_kfid, response["media_id"]) - logger.info("[wechatcom_kf] sendImage url={}, receiver={}".format(img_url, receiver)) + logger.info("[wechat_kf] sendImage url={}, receiver={}".format(img_url, receiver)) elif reply.type == ReplyType.IMAGE: image_storage = reply.content sz = fsize(image_storage) if sz >= 10 * 1024 * 1024: - logger.info("[wechatcom_kf] image too large, compressing, sz={}".format(sz)) + logger.info("[wechat_kf] image too large, compressing, sz={}".format(sz)) image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1) image_storage.seek(0) try: response = self.client.media.upload("image", image_storage) except WeChatClientException as e: - logger.error("[wechatcom_kf] upload image failed: {}".format(e)) + logger.error("[wechat_kf] upload image failed: {}".format(e)) return self._send_image(external_userid, open_kfid, response["media_id"]) - logger.info("[wechatcom_kf] sendImage, receiver={}".format(receiver)) + logger.info("[wechat_kf] sendImage, receiver={}".format(receiver)) elif reply.type == ReplyType.VIDEO_URL: video_url = reply.content @@ -219,10 +219,10 @@ class WechatComKfChannel(ChatChannel): "video", requests.get(video_url, stream=True).content ) except WeChatClientException as e: - logger.error("[wechatcom_kf] upload video failed: {}".format(e)) + logger.error("[wechat_kf] upload video failed: {}".format(e)) return self._send_video(external_userid, open_kfid, response["media_id"]) - logger.info("[wechatcom_kf] sendVideo url={}, receiver={}".format(video_url, receiver)) + logger.info("[wechat_kf] sendVideo url={}, receiver={}".format(video_url, receiver)) elif reply.type == ReplyType.FILE: file_path = reply.content @@ -232,13 +232,13 @@ class WechatComKfChannel(ChatChannel): "file", (os.path.basename(file_path), f.read()) ) except WeChatClientException as e: - logger.error("[wechatcom_kf] upload file failed: {}".format(e)) + logger.error("[wechat_kf] upload file failed: {}".format(e)) return self._send_file(external_userid, open_kfid, response["media_id"]) - logger.info("[wechatcom_kf] sendFile={}, receiver={}".format(file_path, receiver)) + logger.info("[wechat_kf] sendFile={}, receiver={}".format(file_path, receiver)) else: - logger.warning("[wechatcom_kf] unsupported reply type: {}".format(reply.type)) + logger.warning("[wechat_kf] unsupported reply type: {}".format(reply.type)) # ------------------------------------------------------------------ # Inbound — pull messages by cursor @@ -263,9 +263,9 @@ class WechatComKfChannel(ChatChannel): return for raw in msgs: try: - kf_msg = WechatComKfMessage(msg=raw, client=self.client) + kf_msg = WechatKfMessage(msg=raw, client=self.client) except NotImplementedError as e: - logger.debug("[wechatcom_kf] {}".format(e)) + logger.debug("[wechat_kf] {}".format(e)) continue context = self._compose_context( kf_msg.ctype, @@ -300,7 +300,7 @@ class WechatComKfChannel(ChatChannel): break next_cursor = cursor_after logger.info( - "[wechatcom_kf] first-start bootstrap finished for open_kfid={}, " + "[wechat_kf] first-start bootstrap finished for open_kfid={}, " "skipped {} historical messages".format(open_kfid, total_skipped) ) @@ -332,7 +332,7 @@ class WechatComKfChannel(ChatChannel): if collected: collected = _dedup_image_text_pair(collected) logger.info( - "[wechatcom_kf] pulled {} messages for open_kfid={}".format(len(collected), open_kfid) + "[wechat_kf] pulled {} messages for open_kfid={}".format(len(collected), open_kfid) ) return collected @@ -351,12 +351,12 @@ class WechatComKfChannel(ChatChannel): try: resp = requests.post(url, json=payload, timeout=10).json() except Exception as e: - logger.error(f"[wechatcom_kf] sync_msg request failed: {e}") + logger.error(f"[wechat_kf] sync_msg request failed: {e}") return None if resp.get("errcode") != 0: logger.error( - f"[wechatcom_kf] sync_msg errcode={resp.get('errcode')}, " + f"[wechat_kf] sync_msg errcode={resp.get('errcode')}, " f"errmsg={resp.get('errmsg')}, open_kfid={open_kfid}" ) return None @@ -370,10 +370,10 @@ class WechatComKfChannel(ChatChannel): try: resp = requests.post(url, json=payload, timeout=10).json() except Exception as e: - logger.error(f"[wechatcom_kf] send_msg request failed: {e}") + logger.error(f"[wechat_kf] send_msg request failed: {e}") return {"errcode": -1, "errmsg": str(e)} if resp.get("errcode") != 0: - logger.error(f"[wechatcom_kf] send_msg failed, payload={payload}, resp={resp}") + logger.error(f"[wechat_kf] send_msg failed, payload={payload}, resp={resp}") return resp def _send_text(self, external_userid: str, open_kfid: str, content: str) -> dict: @@ -451,9 +451,9 @@ def _dedup_image_text_pair(messages: list) -> list: # ---------------------------------------------------------------------- class Query: def GET(self): - channel = WechatComKfChannel() + channel = WechatKfChannel() params = web.input() - logger.info("[wechatcom_kf] verify params: {}".format(params)) + logger.info("[wechat_kf] verify params: {}".format(params)) try: signature = params.msg_signature timestamp = params.timestamp @@ -465,7 +465,7 @@ class Query: return echostr def POST(self): - channel = WechatComKfChannel() + channel = WechatKfChannel() params = web.input() try: signature = params.msg_signature @@ -474,7 +474,7 @@ class Query: raw_body = web.data() decrypted = channel.crypto.decrypt_message(raw_body, signature, timestamp, nonce) except (InvalidSignatureException, InvalidCorpIdException) as e: - logger.warning(f"[wechatcom_kf] invalid signature: {e}") + logger.warning(f"[wechat_kf] invalid signature: {e}") raise web.Forbidden() # We need the Token + OpenKfId fields from the inner XML to call @@ -483,14 +483,14 @@ class Query: try: root = ET.fromstring(decrypted) except ET.ParseError as e: - logger.error(f"[wechatcom_kf] xml parse error: {e}") + logger.error(f"[wechat_kf] xml parse error: {e}") return "success" msg_type = (root.findtext("MsgType") or "").strip() event = (root.findtext("Event") or "").strip() if msg_type != "event" or event != "kf_msg_or_event": logger.debug( - f"[wechatcom_kf] ignored callback msg_type={msg_type}, event={event}" + f"[wechat_kf] ignored callback msg_type={msg_type}, event={event}" ) return "success" @@ -498,12 +498,12 @@ class Query: open_kfid = root.findtext("OpenKfId") or "" if not token or not open_kfid: logger.warning( - f"[wechatcom_kf] callback missing token or open_kfid: {decrypted}" + f"[wechat_kf] callback missing token or open_kfid: {decrypted}" ) return "success" try: channel.consume_callback(token, open_kfid) except Exception as e: - logger.exception(f"[wechatcom_kf] consume_callback error: {e}") + logger.exception(f"[wechat_kf] consume_callback error: {e}") return "success" diff --git a/channel/wechatcom_kf/wechatcom_kf_cursor_store.py b/channel/wechat_kf/wechat_kf_cursor_store.py similarity index 92% rename from channel/wechatcom_kf/wechatcom_kf_cursor_store.py rename to channel/wechat_kf/wechat_kf_cursor_store.py index 445e2137..72ba3301 100644 --- a/channel/wechatcom_kf/wechatcom_kf_cursor_store.py +++ b/channel/wechat_kf/wechat_kf_cursor_store.py @@ -37,7 +37,7 @@ class CursorStore: with open(self._file_path, "r", encoding="utf-8") as f: return json.load(f) or {} except Exception as e: - logger.warning(f"[wechatcom_kf] failed to load cursor file {self._file_path}: {e}") + logger.warning(f"[wechat_kf] failed to load cursor file {self._file_path}: {e}") return {} def _flush_locked(self): @@ -49,7 +49,7 @@ class CursorStore: json.dump(self._data, f, ensure_ascii=False) os.replace(tmp_path, self._file_path) except Exception as e: - logger.warning(f"[wechatcom_kf] failed to flush cursor file {self._file_path}: {e}") + logger.warning(f"[wechat_kf] failed to flush cursor file {self._file_path}: {e}") try: if os.path.exists(tmp_path): os.remove(tmp_path) diff --git a/channel/wechatcom_kf/wechatcom_kf_message.py b/channel/wechat_kf/wechat_kf_message.py similarity index 90% rename from channel/wechatcom_kf/wechatcom_kf_message.py rename to channel/wechat_kf/wechat_kf_message.py index 85bfdf09..2505dbea 100644 --- a/channel/wechatcom_kf/wechatcom_kf_message.py +++ b/channel/wechat_kf/wechat_kf_message.py @@ -11,7 +11,7 @@ from common.log import logger from common.tmp_dir import TmpDir -class WechatComKfMessage(ChatMessage): +class WechatKfMessage(ChatMessage): """ msg structure (from cgi-bin/kf/sync_msg): { @@ -54,7 +54,7 @@ class WechatComKfMessage(ChatMessage): with open(self.content, "wb") as f: f.write(response.content) else: - logger.info(f"[wechatcom_kf] Failed to download image, {response.content}") + logger.info(f"[wechat_kf] Failed to download image, {response.content}") self._prepare_fn = download_image elif self.msgtype == "voice": @@ -69,12 +69,12 @@ class WechatComKfMessage(ChatMessage): with open(self.content, "wb") as f: f.write(response.content) else: - logger.info(f"[wechatcom_kf] Failed to download voice, {response.content}") + logger.info(f"[wechat_kf] Failed to download voice, {response.content}") self._prepare_fn = download_voice else: raise NotImplementedError( - f"[wechatcom_kf] Unsupported message type: {self.msgtype}" + f"[wechat_kf] Unsupported message type: {self.msgtype}" ) self.from_user_id = self.external_userid diff --git a/common/const.py b/common/const.py index 8227ff7d..dccac1a4 100644 --- a/common/const.py +++ b/common/const.py @@ -227,4 +227,4 @@ DINGTALK = "dingtalk" WECOM_BOT = "wecom_bot" QQ = "qq" WEIXIN = "weixin" -WECHATCOM_KF = "wechatcom_kf" # WeCom customer service (微信客服) channel +WECHAT_KF = "wechat_kf" # WeCom customer service (微信客服) channel diff --git a/config.py b/config.py index 19c78fdc..2463ab0e 100644 --- a/config.py +++ b/config.py @@ -151,12 +151,12 @@ available_setting = { "wechatcomapp_secret": "", # 企业微信app的secret "wechatcomapp_agent_id": "", # 企业微信app的agent_id "wechatcomapp_aes_key": "", # 企业微信app的aes_key - # 微信客服(wechatcom_kf)的配置 - # 注意: 微信客服与企微自建应用是两套不同的应用,共用 corp_id,但 secret/token/aes_key 各自独立 - "wechatcom_kf_token": "", # 微信客服回调token - "wechatcom_kf_port": 9888, # 微信客服回调服务端口 - "wechatcom_kf_secret": "", # 微信客服应用的secret - "wechatcom_kf_aes_key": "", # 微信客服回调aes_key + # 微信客服(wechat_kf)的配置 + "wechat_kf_corp_id": "", # 微信客服所在企业的corp_id + "wechat_kf_token": "", # 微信客服回调token + "wechat_kf_port": 9888, # 微信客服回调服务端口 + "wechat_kf_secret": "", # 微信客服应用的secret + "wechat_kf_aes_key": "", # 微信客服回调aes_key # 飞书配置 "feishu_port": 80, # 飞书bot监听端口,仅webhook模式需要 "feishu_app_id": "", # 飞书机器人应用APP Id @@ -180,7 +180,7 @@ available_setting = { # chatgpt指令自定义触发词 "clear_memory_commands": ["#清除记忆"], # 重置会话指令,必须以#开头 # channel配置 - "channel_type": "", # 通道类型,支持多渠道同时运行。单个: "feishu",多个: "feishu, dingtalk" 或 ["feishu", "dingtalk"]。可选值: web,feishu,dingtalk,wecom_bot,weixin,wechatmp,wechatmp_service,wechatcom_app,wechatcom_kf + "channel_type": "", # 通道类型,支持多渠道同时运行。单个: "feishu",多个: "feishu, dingtalk" 或 ["feishu", "dingtalk"]。可选值: web,feishu,dingtalk,wecom_bot,weixin,wechatmp,wechatmp_service,wechatcom_app,wechat_kf "web_console": True, # 是否自动启动Web控制台(默认启动)。设为False可禁用 "subscribe_msg": "", # 订阅消息, 支持: wechatmp, wechatmp_service, wechatcom_app "debug": False, # 是否开启debug模式,开启后会打印更多日志 @@ -417,9 +417,10 @@ def load_config(): "wechatcomapp_agent_id": "WECHATCOMAPP_AGENT_ID", "wechatcomapp_secret": "WECHATCOMAPP_SECRET", "wechatcom_corp_id": "WECHATCOM_CORP_ID", - "wechatcom_kf_secret": "WECHATCOM_KF_SECRET", - "wechatcom_kf_token": "WECHATCOM_KF_TOKEN", - "wechatcom_kf_aes_key": "WECHATCOM_KF_AES_KEY", + "wechat_kf_corp_id": "WECHAT_KF_CORP_ID", + "wechat_kf_secret": "WECHAT_KF_SECRET", + "wechat_kf_token": "WECHAT_KF_TOKEN", + "wechat_kf_aes_key": "WECHAT_KF_AES_KEY", "qq_app_id": "QQ_APP_ID", "qq_app_secret": "QQ_APP_SECRET", "weixin_token": "WEIXIN_TOKEN", diff --git a/docs/channels/wecom-kf.mdx b/docs/channels/wechat-kf.mdx similarity index 89% rename from docs/channels/wecom-kf.mdx rename to docs/channels/wechat-kf.mdx index 42a13991..ca83aaed 100644 --- a/docs/channels/wecom-kf.mdx +++ b/docs/channels/wechat-kf.mdx @@ -27,7 +27,7 @@ description: 将 CowAgent 接入微信客服(WeCom Customer Service) -2. 点击 **我的企业**,在最下方获取 **企业ID**(后续填写到 `wechatcom_corp_id`): +2. 点击 **我的企业**,在最下方获取 **企业ID**(后续填写到 `wechat_kf_corp_id`): @@ -58,22 +58,22 @@ description: 将 CowAgent 接入微信客服(WeCom Customer Service) ```json { - "channel_type": "wechatcom_kf", - "wechatcom_corp_id": "YOUR_CORP_ID", - "wechatcom_kf_secret": "YOUR_SECRET", - "wechatcom_kf_token": "YOUR_TOKEN", - "wechatcom_kf_aes_key": "YOUR_AES_KEY", - "wechatcom_kf_port": 9888 + "channel_type": "wechat_kf", + "wechat_kf_corp_id": "YOUR_CORP_ID", + "wechat_kf_secret": "YOUR_SECRET", + "wechat_kf_token": "YOUR_TOKEN", + "wechat_kf_aes_key": "YOUR_AES_KEY", + "wechat_kf_port": 9888 } ``` | 参数 | 说明 | | --- | --- | - | `wechatcom_corp_id` | 企业 ID | - | `wechatcom_kf_secret` | 绑定到微信客服的那个企微自建应用的 Secret | - | `wechatcom_kf_token` | API 接收配置中的 Token | - | `wechatcom_kf_aes_key` | API 接收配置中的 EncodingAESKey | - | `wechatcom_kf_port` | 监听端口,默认 9888 | + | `wechat_kf_corp_id` | 企业 ID | + | `wechat_kf_secret` | 绑定到微信客服的那个企微自建应用的 Secret | + | `wechat_kf_token` | API 接收配置中的 Token | + | `wechat_kf_aes_key` | API 接收配置中的 EncodingAESKey | + | `wechat_kf_port` | 监听端口,默认 9888 | diff --git a/docs/docs.json b/docs/docs.json index c5ddd9cd..355f9e9e 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -188,7 +188,7 @@ "channels/wecom-bot", "channels/qq", "channels/wecom", - "channels/wecom-kf", + "channels/wechat-kf", "channels/wechatmp" ] } @@ -381,7 +381,7 @@ "en/channels/wecom-bot", "en/channels/qq", "en/channels/wecom", - "en/channels/wecom-kf", + "en/channels/wechat-kf", "en/channels/wechatmp" ] } @@ -576,7 +576,7 @@ "ja/channels/wecom-bot", "ja/channels/qq", "ja/channels/wecom", - "ja/channels/wecom-kf", + "ja/channels/wechat-kf", "ja/channels/wechatmp" ] } diff --git a/docs/en/channels/wecom-kf.mdx b/docs/en/channels/wechat-kf.mdx similarity index 90% rename from docs/en/channels/wecom-kf.mdx rename to docs/en/channels/wechat-kf.mdx index 1dd973ce..f0711d51 100644 --- a/docs/en/channels/wecom-kf.mdx +++ b/docs/en/channels/wechat-kf.mdx @@ -27,7 +27,7 @@ Required resources: -2. Click **My Enterprise** and find the **Corp ID** at the bottom of the page (it goes into `wechatcom_corp_id`): +2. Click **My Enterprise** and find the **Corp ID** at the bottom of the page (it goes into `wechat_kf_corp_id`): @@ -58,22 +58,22 @@ Fill in the 4 fields collected from the previous step (Corp ID / Secret / Token ```json { - "channel_type": "wechatcom_kf", - "wechatcom_corp_id": "YOUR_CORP_ID", - "wechatcom_kf_secret": "YOUR_SECRET", - "wechatcom_kf_token": "YOUR_TOKEN", - "wechatcom_kf_aes_key": "YOUR_AES_KEY", - "wechatcom_kf_port": 9888 + "channel_type": "wechat_kf", + "wechat_kf_corp_id": "YOUR_CORP_ID", + "wechat_kf_secret": "YOUR_SECRET", + "wechat_kf_token": "YOUR_TOKEN", + "wechat_kf_aes_key": "YOUR_AES_KEY", + "wechat_kf_port": 9888 } ``` | Parameter | Description | | --- | --- | - | `wechatcom_corp_id` | Corp ID | - | `wechatcom_kf_secret` | Secret of the WeCom custom app bound to Customer Service | - | `wechatcom_kf_token` | Token from the API reception config | - | `wechatcom_kf_aes_key` | EncodingAESKey from the API reception config | - | `wechatcom_kf_port` | Listening port, default 9888 | + | `wechat_kf_corp_id` | Corp ID | + | `wechat_kf_secret` | Secret of the WeCom custom app bound to Customer Service | + | `wechat_kf_token` | Token from the API reception config | + | `wechat_kf_aes_key` | EncodingAESKey from the API reception config | + | `wechat_kf_port` | Listening port, default 9888 | diff --git a/docs/ja/channels/wecom-kf.mdx b/docs/ja/channels/wechat-kf.mdx similarity index 91% rename from docs/ja/channels/wecom-kf.mdx rename to docs/ja/channels/wechat-kf.mdx index 25de67ff..078f1b5a 100644 --- a/docs/ja/channels/wecom-kf.mdx +++ b/docs/ja/channels/wechat-kf.mdx @@ -27,7 +27,7 @@ WeCom の自建アプリを「微信客服(WeCom Customer Service)」アカ -2. **自社情報** をクリックし、ページ下部で **企業ID(Corp ID)** を確認します(`wechatcom_corp_id` に設定します): +2. **自社情報** をクリックし、ページ下部で **企業ID(Corp ID)** を確認します(`wechat_kf_corp_id` に設定します): @@ -58,22 +58,22 @@ WeCom の自建アプリを「微信客服(WeCom Customer Service)」アカ ```json { - "channel_type": "wechatcom_kf", - "wechatcom_corp_id": "YOUR_CORP_ID", - "wechatcom_kf_secret": "YOUR_SECRET", - "wechatcom_kf_token": "YOUR_TOKEN", - "wechatcom_kf_aes_key": "YOUR_AES_KEY", - "wechatcom_kf_port": 9888 + "channel_type": "wechat_kf", + "wechat_kf_corp_id": "YOUR_CORP_ID", + "wechat_kf_secret": "YOUR_SECRET", + "wechat_kf_token": "YOUR_TOKEN", + "wechat_kf_aes_key": "YOUR_AES_KEY", + "wechat_kf_port": 9888 } ``` | パラメータ | 説明 | | --- | --- | - | `wechatcom_corp_id` | 企業 ID | - | `wechatcom_kf_secret` | カスタマーサービスにバインドした企業微信自建アプリの Secret | - | `wechatcom_kf_token` | API 受信設定の Token | - | `wechatcom_kf_aes_key` | API 受信設定の EncodingAESKey | - | `wechatcom_kf_port` | リスンポート、デフォルトは 9888 | + | `wechat_kf_corp_id` | 企業 ID | + | `wechat_kf_secret` | カスタマーサービスにバインドした企業微信自建アプリの Secret | + | `wechat_kf_token` | API 受信設定の Token | + | `wechat_kf_aes_key` | API 受信設定の EncodingAESKey | + | `wechat_kf_port` | リスンポート、デフォルトは 9888 |