mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-28 13:17:14 +00:00
Fix wecomcs open_kfid msgid (#2449)
* Update wecomcs.py fix bug wecomcs send_message open_kfid event.receiver_id is open_kfid * Update wecomcs.py fix bug msgid exceeds the 32-byte limit * test(wecomcs): cover bounded message IDs and images --------- Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
This commit is contained in:
@@ -295,6 +295,34 @@ class WecomCSClient:
|
|||||||
raise Exception('Failed to send message')
|
raise Exception('Failed to send message')
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@_bounded_token_retry
|
||||||
|
async def send_image_msg(self, open_kfid: str, external_userid: str, msgid: str, media_id: str):
|
||||||
|
if not await self.check_access_token():
|
||||||
|
self.access_token = await self.get_access_token(self.secret)
|
||||||
|
|
||||||
|
url = f'{self.base_url}/kf/send_msg?access_token={self.access_token}'
|
||||||
|
payload = {
|
||||||
|
'touser': external_userid,
|
||||||
|
'open_kfid': open_kfid,
|
||||||
|
'msgid': msgid,
|
||||||
|
'msgtype': 'image',
|
||||||
|
'image': {
|
||||||
|
'media_id': media_id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
async with self._http_client_context() as client:
|
||||||
|
response = await client.post(url, json=payload)
|
||||||
|
|
||||||
|
data = await httpclient.parse_json_response(response)
|
||||||
|
if data['errcode'] == 40014 or data['errcode'] == 42001:
|
||||||
|
self.access_token = await self.get_access_token(self.secret)
|
||||||
|
return await self.send_image_msg(open_kfid, external_userid, msgid, media_id)
|
||||||
|
if data['errcode'] != 0:
|
||||||
|
await self.logger.error(f'发送图片失败:{data}')
|
||||||
|
raise Exception('Failed to send image message')
|
||||||
|
return data
|
||||||
|
|
||||||
async def handle_callback_request(self):
|
async def handle_callback_request(self):
|
||||||
"""处理回调请求(独立端口模式,使用全局 request)。"""
|
"""处理回调请求(独立端口模式,使用全局 request)。"""
|
||||||
return await self._handle_callback_internal(request)
|
return await self._handle_callback_internal(request)
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class WecomEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
|||||||
if event.type == 'text':
|
if event.type == 'text':
|
||||||
yiri_chain = await WecomMessageConverter.target2yiri(event.message, event.message_id)
|
yiri_chain = await WecomMessageConverter.target2yiri(event.message, event.message_id)
|
||||||
friend = platform_entities.Friend(
|
friend = platform_entities.Friend(
|
||||||
id=f'u{event.user_id}',
|
id=f'{event.receiver_id}|u{event.user_id}',
|
||||||
nickname=nickname,
|
nickname=nickname,
|
||||||
remark='',
|
remark='',
|
||||||
)
|
)
|
||||||
@@ -117,7 +117,7 @@ class WecomEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
|||||||
)
|
)
|
||||||
elif event.type == 'image':
|
elif event.type == 'image':
|
||||||
friend = platform_entities.Friend(
|
friend = platform_entities.Friend(
|
||||||
id=f'u{event.user_id}',
|
id=f'{event.receiver_id}|u{event.user_id}',
|
||||||
nickname=nickname,
|
nickname=nickname,
|
||||||
remark='',
|
remark='',
|
||||||
)
|
)
|
||||||
@@ -197,7 +197,7 @@ class WecomCSAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
|||||||
|
|
||||||
content_list = await WecomMessageConverter.yiri2target(message, self.bot)
|
content_list = await WecomMessageConverter.yiri2target(message, self.bot)
|
||||||
for content in content_list:
|
for content in content_list:
|
||||||
msgid = f'langbot_{uuid.uuid4().hex}'
|
msgid = f'{uuid.uuid4().hex}'
|
||||||
if content['type'] == 'text':
|
if content['type'] == 'text':
|
||||||
await self.bot.send_text_msg(
|
await self.bot.send_text_msg(
|
||||||
open_kfid=open_kfid,
|
open_kfid=open_kfid,
|
||||||
@@ -205,6 +205,13 @@ class WecomCSAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
|||||||
msgid=msgid,
|
msgid=msgid,
|
||||||
content=content['content'],
|
content=content['content'],
|
||||||
)
|
)
|
||||||
|
elif content['type'] == 'image':
|
||||||
|
await self.bot.send_image_msg(
|
||||||
|
open_kfid=open_kfid,
|
||||||
|
external_userid=external_userid,
|
||||||
|
msgid=msgid,
|
||||||
|
media_id=content['media_id'],
|
||||||
|
)
|
||||||
|
|
||||||
def set_bot_uuid(self, bot_uuid: str):
|
def set_bot_uuid(self, bot_uuid: str):
|
||||||
"""设置 bot UUID(用于生成 webhook URL)"""
|
"""设置 bot UUID(用于生成 webhook URL)"""
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import uuid
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
@@ -49,7 +50,29 @@ async def test_send_message_sends_text_to_customer_service_user():
|
|||||||
assert kwargs['open_kfid'] == 'kf-test'
|
assert kwargs['open_kfid'] == 'kf-test'
|
||||||
assert kwargs['external_userid'] == 'external-user'
|
assert kwargs['external_userid'] == 'external-user'
|
||||||
assert kwargs['content'] == 'hello'
|
assert kwargs['content'] == 'hello'
|
||||||
assert kwargs['msgid'].startswith('langbot_')
|
assert len(kwargs['msgid'].encode()) <= 32
|
||||||
|
assert uuid.UUID(hex=kwargs['msgid']).hex == kwargs['msgid']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_send_message_sends_image_to_customer_service_user():
|
||||||
|
adapter = make_adapter()
|
||||||
|
adapter.bot_account_id = 'kf-test'
|
||||||
|
adapter.bot = SimpleNamespace(
|
||||||
|
get_media_id=AsyncMock(return_value='media-id'),
|
||||||
|
send_image_msg=AsyncMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
message = platform_message.MessageChain([platform_message.Image(base64='aW1hZ2U=')])
|
||||||
|
|
||||||
|
await adapter.send_message('person', 'uexternal-user', message)
|
||||||
|
|
||||||
|
adapter.bot.send_image_msg.assert_awaited_once()
|
||||||
|
kwargs = adapter.bot.send_image_msg.await_args.kwargs
|
||||||
|
assert kwargs['open_kfid'] == 'kf-test'
|
||||||
|
assert kwargs['external_userid'] == 'external-user'
|
||||||
|
assert kwargs['media_id'] == 'media-id'
|
||||||
|
assert len(kwargs['msgid'].encode()) <= 32
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from langbot.libs.wecom_customer_service_api.api import WecomCSClient
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_send_image_msg_posts_customer_service_image_payload() -> None:
|
||||||
|
captured_request: httpx.Request | None = None
|
||||||
|
|
||||||
|
def handle_request(request: httpx.Request) -> httpx.Response:
|
||||||
|
nonlocal captured_request
|
||||||
|
captured_request = request
|
||||||
|
return httpx.Response(200, json={'errcode': 0})
|
||||||
|
|
||||||
|
client = WecomCSClient(
|
||||||
|
corpid='corp-id',
|
||||||
|
secret='secret',
|
||||||
|
token='token',
|
||||||
|
EncodingAESKey='encoding-key',
|
||||||
|
logger=None,
|
||||||
|
unified_mode=True,
|
||||||
|
)
|
||||||
|
client.access_token = 'access-token'
|
||||||
|
client._http_client = httpx.AsyncClient(transport=httpx.MockTransport(handle_request))
|
||||||
|
|
||||||
|
try:
|
||||||
|
await client.send_image_msg(
|
||||||
|
open_kfid='kf-test',
|
||||||
|
external_userid='external-user',
|
||||||
|
msgid='a' * 32,
|
||||||
|
media_id='media-id',
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
assert captured_request is not None
|
||||||
|
assert captured_request.url.path == '/cgi-bin/kf/send_msg'
|
||||||
|
assert captured_request.url.params['access_token'] == 'access-token'
|
||||||
|
assert captured_request.method == 'POST'
|
||||||
|
assert captured_request.read().decode() == (
|
||||||
|
'{"touser":"external-user","open_kfid":"kf-test","msgid":"'
|
||||||
|
+ 'a' * 32
|
||||||
|
+ '","msgtype":"image","image":{"media_id":"media-id"}}'
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user