From 30945aafddaf4c7799e546e0340b4d730742393e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 21:04:55 +0800 Subject: [PATCH] feat: support configurable WeCom API base URL for reverse proxy deployment (#1890) * Initial plan * Add api_base_url support to WeCom API libraries and adapters Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * Add api_base_url parameter to OAClient and adapters for Official Account and WeCom APIs --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> Co-authored-by: Junyan Qin --- src/langbot/libs/official_account_api/api.py | 16 +++++++++++++--- src/langbot/libs/wecom_api/api.py | 5 +++-- .../libs/wecom_customer_service_api/api.py | 17 +++++++++++++---- .../pkg/platform/sources/officialaccount.py | 2 ++ .../pkg/platform/sources/officialaccount.yaml | 10 ++++++++++ src/langbot/pkg/platform/sources/wecom.py | 1 + src/langbot/pkg/platform/sources/wecom.yaml | 10 ++++++++++ src/langbot/pkg/platform/sources/wecomcs.py | 1 + src/langbot/pkg/platform/sources/wecomcs.yaml | 10 ++++++++++ 9 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/langbot/libs/official_account_api/api.py b/src/langbot/libs/official_account_api/api.py index 671f49a4..b474205d 100644 --- a/src/langbot/libs/official_account_api/api.py +++ b/src/langbot/libs/official_account_api/api.py @@ -23,12 +23,21 @@ xml_template = """ class OAClient: - def __init__(self, token: str, EncodingAESKey: str, AppID: str, Appsecret: str, logger: None, unified_mode: bool = False): + def __init__( + self, + token: str, + EncodingAESKey: str, + AppID: str, + Appsecret: str, + logger: None, + unified_mode: bool = False, + api_base_url: str = 'https://api.weixin.qq.com', + ): self.token = token self.aes = EncodingAESKey self.appid = AppID self.appsecret = Appsecret - self.base_url = 'https://api.weixin.qq.com' + self.base_url = api_base_url self.access_token = '' self.unified_mode = unified_mode self.app = Quart(__name__) @@ -208,12 +217,13 @@ class OAClientForLongerResponse: LoadingMessage: str, logger: None, unified_mode: bool = False, + api_base_url: str = 'https://api.weixin.qq.com', ): self.token = token self.aes = EncodingAESKey self.appid = AppID self.appsecret = Appsecret - self.base_url = 'https://api.weixin.qq.com' + self.base_url = api_base_url self.access_token = '' self.unified_mode = unified_mode self.app = Quart(__name__) diff --git a/src/langbot/libs/wecom_api/api.py b/src/langbot/libs/wecom_api/api.py index 1b8591fd..948bac95 100644 --- a/src/langbot/libs/wecom_api/api.py +++ b/src/langbot/libs/wecom_api/api.py @@ -22,13 +22,14 @@ class WecomClient: contacts_secret: str, logger: None, unified_mode: bool = False, + api_base_url: str = 'https://qyapi.weixin.qq.com/cgi-bin', ): self.corpid = corpid self.secret = secret self.access_token_for_contacts = '' self.token = token self.aes = EncodingAESKey - self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin' + self.base_url = api_base_url self.access_token = '' self.secret_for_contacts = contacts_secret self.logger = logger @@ -56,7 +57,7 @@ class WecomClient: return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip()) async def get_access_token(self, secret): - url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={secret}' + url = f'{self.base_url}/gettoken?corpid={self.corpid}&corpsecret={secret}' async with httpx.AsyncClient() as client: response = await client.get(url) data = response.json() diff --git a/src/langbot/libs/wecom_customer_service_api/api.py b/src/langbot/libs/wecom_customer_service_api/api.py index 3c4e0fab..e1b94879 100644 --- a/src/langbot/libs/wecom_customer_service_api/api.py +++ b/src/langbot/libs/wecom_customer_service_api/api.py @@ -13,13 +13,22 @@ import aiofiles class WecomCSClient: - def __init__(self, corpid: str, secret: str, token: str, EncodingAESKey: str, logger: None, unified_mode: bool = False): + def __init__( + self, + corpid: str, + secret: str, + token: str, + EncodingAESKey: str, + logger: None, + unified_mode: bool = False, + api_base_url: str = 'https://qyapi.weixin.qq.com/cgi-bin', + ): self.corpid = corpid self.secret = secret self.access_token_for_contacts = '' self.token = token self.aes = EncodingAESKey - self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin' + self.base_url = api_base_url self.access_token = '' self.logger = logger self.unified_mode = unified_mode @@ -66,7 +75,7 @@ class WecomCSClient: return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip()) async def get_access_token(self, secret): - url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={secret}' + url = f'{self.base_url}/gettoken?corpid={self.corpid}&corpsecret={secret}' async with httpx.AsyncClient() as client: response = await client.get(url) data = response.json() @@ -172,7 +181,7 @@ class WecomCSClient: if not await self.check_access_token(): self.access_token = await self.get_access_token(self.secret) - url = f'https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token={self.access_token}' + url = f'{self.base_url}/kf/send_msg?access_token={self.access_token}' payload = { 'touser': external_userid, diff --git a/src/langbot/pkg/platform/sources/officialaccount.py b/src/langbot/pkg/platform/sources/officialaccount.py index e0c48a23..288991d6 100644 --- a/src/langbot/pkg/platform/sources/officialaccount.py +++ b/src/langbot/pkg/platform/sources/officialaccount.py @@ -76,6 +76,7 @@ class OfficialAccountAdapter(abstract_platform_adapter.AbstractMessagePlatformAd AppID=config['AppID'], logger=logger, unified_mode=True, + api_base_url=config.get('api_base_url', 'https://api.weixin.qq.com'), ) elif config['Mode'] == 'passive': bot = OAClientForLongerResponse( @@ -86,6 +87,7 @@ class OfficialAccountAdapter(abstract_platform_adapter.AbstractMessagePlatformAd LoadingMessage=config.get('LoadingMessage', ''), logger=logger, unified_mode=True, + api_base_url=config.get('api_base_url', 'https://api.weixin.qq.com'), ) else: raise KeyError('请设置微信公众号通信模式') diff --git a/src/langbot/pkg/platform/sources/officialaccount.yaml b/src/langbot/pkg/platform/sources/officialaccount.yaml index 53345413..fda0a912 100644 --- a/src/langbot/pkg/platform/sources/officialaccount.yaml +++ b/src/langbot/pkg/platform/sources/officialaccount.yaml @@ -53,6 +53,16 @@ spec: type: string required: true default: "AI正在思考中,请发送任意内容获取回复。" + - name: api_base_url + label: + en_US: API Base URL + zh_Hans: API 基础 URL + description: + en_US: API Base URL, used for accessing the Official Account API. If you are deploying in an internal network environment and accessing the Official Account API through a reverse proxy, please fill in this item according to the documentation. + zh_Hans: 可选,若您部署在内网环境并通过反向代理访问微信公众号 API,可根据文档修改此项 + type: string + required: false + default: "https://api.weixin.qq.com" execution: python: path: ./officialaccount.py diff --git a/src/langbot/pkg/platform/sources/wecom.py b/src/langbot/pkg/platform/sources/wecom.py index c42d0c9c..c6625040 100644 --- a/src/langbot/pkg/platform/sources/wecom.py +++ b/src/langbot/pkg/platform/sources/wecom.py @@ -170,6 +170,7 @@ class WecomAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): contacts_secret=config['contacts_secret'], logger=logger, unified_mode=True, + api_base_url=config.get('api_base_url', 'https://qyapi.weixin.qq.com/cgi-bin'), ) super().__init__( diff --git a/src/langbot/pkg/platform/sources/wecom.yaml b/src/langbot/pkg/platform/sources/wecom.yaml index 98fad4ce..1d547c29 100644 --- a/src/langbot/pkg/platform/sources/wecom.yaml +++ b/src/langbot/pkg/platform/sources/wecom.yaml @@ -46,6 +46,16 @@ spec: type: string required: true default: "" + - name: api_base_url + label: + en_US: API Base URL + zh_Hans: API 基础 URL + description: + en_US: API Base URL, used for accessing the WeCom API. If you are deploying in an internal network environment and accessing the WeCom Customer Service API through a reverse proxy, please fill in this item according to the documentation. + zh_Hans: 可选,若您部署在内网环境并通过反向代理访问企业微信 API,可根据文档填写此项 + type: string + required: false + default: "https://qyapi.weixin.qq.com/cgi-bin" execution: python: path: ./wecom.py diff --git a/src/langbot/pkg/platform/sources/wecomcs.py b/src/langbot/pkg/platform/sources/wecomcs.py index bfe6811b..536429cc 100644 --- a/src/langbot/pkg/platform/sources/wecomcs.py +++ b/src/langbot/pkg/platform/sources/wecomcs.py @@ -141,6 +141,7 @@ class WecomCSAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): EncodingAESKey=config['EncodingAESKey'], logger=logger, unified_mode=True, + api_base_url=config.get('api_base_url', 'https://qyapi.weixin.qq.com/cgi-bin'), ) super().__init__( diff --git a/src/langbot/pkg/platform/sources/wecomcs.yaml b/src/langbot/pkg/platform/sources/wecomcs.yaml index 7d4f398d..a1be068e 100644 --- a/src/langbot/pkg/platform/sources/wecomcs.yaml +++ b/src/langbot/pkg/platform/sources/wecomcs.yaml @@ -39,6 +39,16 @@ spec: type: string required: true default: "" + - name: api_base_url + label: + en_US: API Base URL + zh_Hans: API 基础 URL + description: + en_US: API Base URL, used for accessing the WeCom API. If you are deploying in an internal network environment and accessing the WeCom Customer Service API through a reverse proxy, please fill in this item according to the documentation. + zh_Hans: 可选,若您部署在内网环境并通过反向代理访问企业微信 API,可根据文档修改此项 + type: string + required: false + default: "https://qyapi.weixin.qq.com/cgi-bin" execution: python: path: ./wecomcs.py