mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
refactor(wechat_kf): rename channel from wechatcom_kf and split corp_id
Rename the WeCom customer-service channel and give it its own corp_id
field so users no longer have to share `wechatcom_corp_id` with the
self-built WeCom app channel.
Renames (channel-side):
- channel type / const: wechatcom_kf -> wechat_kf
- package dir: channel/wechatcom_kf/ -> channel/wechat_kf/
- python files / classes: WechatComKf* -> WechatKf*
- config keys: wechatcom_kf_{secret,token,aes_key,port} ->
wechat_kf_{secret,token,aes_key,port}; new wechat_kf_corp_id
- env vars: WECHATCOM_KF_* -> WECHAT_KF_*; new WECHAT_KF_CORP_ID
- log prefix / cursor file: [wechatcom_kf] -> [wechat_kf]
- web console CHANNEL_DEFS key + startup log line
Renames (docs):
- docs/channels/wecom-kf.mdx -> docs/channels/wechat-kf.mdx (zh/en/ja)
- update docs.json sidebar entries and all field names inside the docs
In addition, the Web Console "微信客服" entry now exposes its own
Corp ID field instead of reusing the wechatcom_app one, and includes
the screenshot of the visual config in the channel guide.
Web Console onboarding section is added (Tabs: Web Console / config
file) and the local URL `http://127.0.0.1:9899/` parenthetical is
dropped for consistency with other channel docs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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": "<EncodingAESKey>",
|
||||
"wechatcom_kf_port": 9888
|
||||
"wechat_kf_corp_id": "ww1234567890abcdef",
|
||||
"wechat_kf_secret": "<企微应用的 Secret>",
|
||||
"wechat_kf_token": "<接收消息 Token>",
|
||||
"wechat_kf_aes_key": "<EncodingAESKey>",
|
||||
"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`,验证成功。
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user