Skip to content

feat(integrations/wechat): implement integration#14940

Merged
JustinBordage merged 77 commits intomasterfrom
jbord_wechat-implement-integration
Mar 16, 2026
Merged

feat(integrations/wechat): implement integration#14940
JustinBordage merged 77 commits intomasterfrom
jbord_wechat-implement-integration

Conversation

@JustinBordage
Copy link
Contributor

@JustinBordage JustinBordage commented Feb 12, 2026

Resolves SH-423

@JustinBordage
Copy link
Contributor Author

This PR is a rework of the integration from PR #14852. It cleans up the logic making it easier to maintain and using best practices from the Botpress organization.

@linear
Copy link

linear bot commented Feb 12, 2026

@JustinBordage JustinBordage requested review from a team as code owners March 13, 2026 23:01
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR introduces a new WeChat Official Account integration for Botpress, implementing inbound webhook message handling (text, image, video, voice, location, link), outbound messaging (text, image, and video-as-text), access token caching with expiry refresh, media download/upload via Botpress file storage, and webhook signature verification.

Key observations:

  • Several bugs identified in earlier review rounds have been fixed in this commit: the token expiry comparison units/direction in auth.ts, the nested XML wrapper in the primary parser (inbound.ts), the missing formData body in axios.post, and the incorrect resp vs resp.data passed to wechatVideoUrlRespSchema.
  • Known remaining bug — video media downloads always fail: In client.ts, the retry guard if (!retry) throw is placed at the top of _downloadWeChatMediaFromUrl. The recursive call for video (line 57) passes retry=false, which causes an immediate throw before any network request is made. The developer has acknowledged this and a fix is planned for an upcoming commit.
  • Known remaining bug — WeChat event messages return 500: msgId is still z.string().min(1) (required) in schemas.ts, but WeChat event pushes (subscribe, unsubscribe, menu clicks) do not include MsgId. These events will fail validation and return HTTP 500, causing WeChat to log delivery errors. Developer has acknowledged; fix is planned.
  • New: getMediaUrl has a redundant public parameter — the method is public and takes accessToken as an argument, but the class already stores it privately. The method should be private and use the stored token directly, consistent with sendMessage and uploadMedia.
  • New: Fallback parser logs at error level on success — when the fallback XML parser successfully handles a message, it logs at error level instead of warn, creating misleading noise in error monitoring.

Confidence Score: 2/5

  • Not safe to merge — two known functional bugs (video downloads always fail, event messages return 500) remain unaddressed in the current commit.
  • Several bugs flagged in previous review rounds have been correctly fixed in this commit (auth token units, XML parser nesting, formData body, resp.data parsing). However, two acknowledged bugs are still present: (1) the retry guard in _downloadWeChatMediaFromUrl makes every WeChat video media download throw before touching the network, and (2) msgId remains required in the schema causing all WeChat event push messages (subscribe/unsubscribe/menu) to return HTTP 500. These are not edge cases — any user subscribing to the Official Account or clicking a menu item will trigger a 500 error.
  • integrations/wechat/src/api/client.ts (retry guard bug) and integrations/wechat/src/channels/schemas.ts (required msgId) need fixes before merging.

Important Files Changed

Filename Overview
integrations/wechat/src/api/auth.ts Token caching and refresh logic is implemented correctly — expiresAt is stored in seconds, comparison uses Date.now() / MS_PER_SECOND, and the expiry buffer is applied properly.
integrations/wechat/src/api/client.ts Two remaining open issues: (1) the retry guard (if (!retry) throw) sits before the network call, so any video download via _downloadWeChatMediaFromUrl(videoUrl, false) always throws immediately and video media downloads always fail; (2) getMediaUrl is unnecessarily public and takes a redundant accessToken parameter that should use the stored private field instead.
integrations/wechat/src/channels/inbound.ts Primary XML parser is now correctly wrapped under z.object({ xml: wechatMessageSchema }) (previous bug fixed). However, msgId remains required in the schema so WeChat event push messages (subscribe/unsubscribe) will still cause 500 responses. Also, the fallback parser logs at error level even when it succeeds.
integrations/wechat/src/channels/schemas.ts msgId is still z.string().min(1) (required), meaning all WeChat event push messages (subscribe, unsubscribe, menu clicks) will fail schema validation and receive HTTP 500. Developer has acknowledged this and noted a fix is in an upcoming commit.
integrations/wechat/src/channels/outbound.ts Image sending correctly downloads, uploads to WeChat, and sends via media_id. Video is intentionally sent as a text URL (WeChat does not support video media uploads). Message type definitions are duplicated from client.ts — minor style concern.
integrations/wechat/src/webhook/handler.ts Handler correctly routes GET (challenge) and POST (message) requests, verifies signature first, and propagates error status codes from the result. Clean implementation.

Sequence Diagram

sequenceDiagram
    participant WX as WeChat Platform
    participant H as handler.ts
    participant S as signature.ts
    participant I as inbound.ts
    participant C as WeChatClient
    participant A as auth.ts
    participant BP as Botpress SDK

    WX->>H: POST /webhook (XML message)
    H->>S: verifyWebhookSignature()
    S-->>H: valid/invalid
    alt Invalid signature
        H-->>WX: 403 Forbidden
    end
    H->>I: processInboundChannelMessage()
    I->>I: _parseAndValidateWeChatXmlMessage() [primary parser]
    alt Primary parser fails
        I->>I: _fallbackParseAndValidateWeChatMessage() [regex parser]
    end
    I->>BP: getOrCreateConversation()
    I->>BP: getOrCreateUser()
    alt text message
        I->>BP: createMessage(text)
    else image/video message
        I->>A: getOrRefreshAccessToken()
        A->>A: check cached token expiry
        alt Token expired or missing
            A->>WX: GET /token (fetch fresh token)
            A->>BP: setState (cache token)
        end
        I->>C: downloadWeChatMedia(mediaId)
        C->>WX: GET /media/get
        I->>BP: uploadFile()
        I->>BP: createMessage(image/video)
    else voice message
        I->>BP: createMessage(text "[Voice Message]...")
    else location/link message
        I->>BP: createMessage(text)
    end
    H-->>WX: 200 OK

    Note over WX,H: Outbound (bot → user)
    BP->>H: channel.messages.text/image/video
    H->>A: getOrRefreshAccessToken()
    alt image
        H->>C: downloadMediaFromURL() + uploadMedia()
        C->>WX: POST /media/upload
    end
    H->>C: sendMessage()
    C->>WX: POST /message/custom/send
Loading

Last reviewed commit: bcc980b

Math-Fauch
Math-Fauch previously approved these changes Mar 14, 2026
Math-Fauch
Math-Fauch previously approved these changes Mar 16, 2026
@JustinBordage JustinBordage merged commit edd902e into master Mar 16, 2026
9 checks passed
@JustinBordage JustinBordage deleted the jbord_wechat-implement-integration branch March 16, 2026 19:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants