Help us shape the future of Go! Take the 2026 Go Developer Survey

wechat

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Overview

Package wechat provides personal-WeChat integration via the iLink protocol. It bridges messages between `pkg/messaging/wechat/ilink` (the protocol layer) and the platform-neutral `pkg/messaging` abstractions consumed by `cmd/web/handler/im_gateway.go`.

Protocol layer source

Files under `pkg/messaging/wechat/ilink/` and portions of `pkg/messaging/wechat/{cdn,media}.go` are ported from https://github.com/fastclaw-ai/weclaw at **v0.8.0**.

The iLink protocol is private to Tencent and may change without notice. Keep the protocol layer in sync with upstream: run scripts/update-weclaw.sh, review the result, and bump the pin when a new release addresses a protocol break. Upstream diff URL template:

https://github.com/fastclaw-ai/weclaw/compare/v0.8.0...{new-tag}

Upstream weclaw is MIT-licensed; every ported file carries an attribution header pointing back to upstream.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RemoveUserTokens

func RemoveUserTokens(botID string) error

RemoveUserTokens removes one bot account's persisted conversation tokens. The whole file is deleted once no bot partitions remain.

func StartLogin

func StartLogin(ctx context.Context) (qrcode string, imgBase64 string, err error)

StartLogin kicks off a scan-to-login attempt. Returns:

  • qrcode: opaque handle the frontend passes back to the status-poll API.
  • imgBase64: base64-encoded PNG the frontend can render in <img src>.

iLink's QRCodeImgContent is the URL string that has to be encoded INTO the QR image (not a ready-to-use image), so we run it through rsc.io/qr here rather than leaving QR rendering to the browser.

Types

type CredentialsProvider

type CredentialsProvider func() []*ilink.Credentials

CredentialsProvider supplies the list of iLink credentials currently loadable from disk. Manager / Replier call this lazily so scan-to-login can refresh credentials at runtime (via Manager.Restart()) without any restart of the backend process.

For v1 only the first entry is used; extra entries are ignored with a warning. Multi-account support is tracked in doc §4.5.

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

Listener converts iLink WeixinMessage events into platform-neutral messaging.Message values and feeds them to the handler. It is a thin shim over ilink.Monitor — the actual long-poll loop lives there.

func NewListener

func NewListener(handler messaging.EventHandler, replier *Replier, credsProvider CredentialsProvider, opts ...Option) *Listener

NewListener constructs a Listener. The Replier argument is required: on every incoming message the Listener calls replier.RegisterIncoming so ReplyText(messageID) can later resolve the ContextToken in O(1).

func (*Listener) IsExpired

func (l *Listener) IsExpired() bool

IsExpired reports whether the underlying monitor has observed an unrecoverable session-expired state. Returns false when the listener has never been started.

func (*Listener) Start

func (l *Listener) Start(ctx context.Context) error

Start runs the iLink long-poll monitor on the first configured account. Returns immediately (nil error) if no credentials are saved — this is the expected state before the first scan-to-login. Blocks until ctx is cancelled.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager owns the lifecycle of the WeChat listener goroutine. It mirrors pkg/messaging/lark.Manager — the same Start/Restart/Stop semantics so upstream handler code can treat the two platforms symmetrically.

func NewManager

func NewManager(handler messaging.EventHandler, replier *Replier, credsProvider CredentialsProvider) *Manager

NewManager wires the Manager to the shared imGateway (as handler) and the Replier that will be used for every reply. The Replier must be the same instance registered with the imGateway — it doubles as the incoming-msg metadata cache (see Replier.RegisterIncoming in listener.go).

func (*Manager) IsExpired

func (m *Manager) IsExpired() bool

IsExpired reports whether the current listener's session (bot_token) has been observed to be unrecoverable. Returns false when no listener is running (pre-login, post-logout).

func (*Manager) Restart

func (m *Manager) Restart()

Restart tears down the current listener goroutine and starts a fresh one from the latest credentials. Called by wechat_login_api.go after saving new credentials (login) or removing them (logout).

func (*Manager) Start

func (m *Manager) Start(ctx context.Context)

Start kicks off the listener if credentials are present. Never blocks: the long-poll loop runs on its own goroutine. When no credentials are saved yet (pre-login), logs an info line and returns — Restart() brings the listener up after scan-to-login persists a Credentials file.

func (*Manager) Stop

func (m *Manager) Stop()

Stop cancels the listener context. Called from the process shutdown path (cmd/web/main.go via Handler.StopIMListeners) so the iLink long-poll goroutine exits before HTTP shutdown completes, instead of being torn down implicitly when the root context cancels.

type Option

type Option func(*Listener)

Option configures a Listener. No options are exposed for v1 — kept as a stable extension point so later phases (multi-account, custom message handlers, test injection) don't break the NewListener signature.

type Replier

type Replier struct {
	// contains filtered or unexported fields
}

Replier implements messaging.Replier for WeChat. It keeps an in-memory cache of incoming-message metadata so ReplyText(messageID, ...) can resolve the matching FromUserID/ContextToken in O(1) without touching the append-only im_message repository.

The cache is populated by RegisterIncoming, called by the Listener on every incoming message *before* the messaging.Message is dispatched to the handler.

func NewReplier

func NewReplier(credsProvider CredentialsProvider) *Replier

NewReplier constructs a Replier that resolves credentials via the given provider. The provider is called lazily so scan-to-login can wire in new credentials without re-constructing the Replier. The persisted user-token map (see token_store.go) is loaded eagerly so proactive SendText calls keep working across restarts.

func (*Replier) Close

func (r *Replier) Close()

Close stops the background msgMeta GC goroutine. Safe to call multiple times; safe to skip entirely in production (the goroutine is cleaned up when the process exits). Tests use t.Cleanup(r.Close) to avoid leaking one goroutine per test case.

func (*Replier) RefreshCredentials

func (r *Replier) RefreshCredentials()

RefreshCredentials drops cached clients and message-level reply metadata, then aligns the proactive-send token cache with the currently configured bot. Restarting the same account preserves its persisted user tokens; switching accounts or logging out replaces them with the matching bot partition.

func (*Replier) RegisterIncoming

func (r *Replier) RegisterIncoming(msg *ilink.WeixinMessage, botID string)

RegisterIncoming records the metadata needed to reply to msg later. Called by the Listener for every incoming message. msg.ContextToken can be empty (iLink may drop it for some message types); reply logic already handles the empty-token case by falling back to a fresh client ID.

func (*Replier) ReplyMedia

func (r *Replier) ReplyMedia(ctx context.Context, messageID string, media messaging.MediaPayload) error

ReplyMedia replies to the message with a native WeChat media attachment. The messageID lookup mirrors ReplyText so media replies are sent to the same conversation/thread as the triggering incoming message.

func (*Replier) ReplyText

func (r *Replier) ReplyText(ctx context.Context, messageID, content string) error

ReplyText replies to the message with the given messageID. Looks up the target user and ContextToken via the msgMeta cache populated by the listener's RegisterIncoming. On a miss (message older than msgMetaTTL, or received before a quartet restart), refuses to send and returns an error — silently addressing the wrong user is worse than dropping the reply, and the messaging.Replier interface does not give us enough information (no senderID) to safely fall back to userToken. Callers that know the sender can use SendText(toUserID, content) instead.

func (*Replier) SendText

func (r *Replier) SendText(ctx context.Context, toUserID, content string) error

SendText proactively sends a text message to the given iLink user ID. Used for scheduled jobs / loop completions (not currently wired up but exposed per doc §9.2). Requires a prior incoming message from toUserID so we have a ContextToken on hand — errors if no history exists.

Directories

Path Synopsis
Package cdn talks to the WeChat C2C CDN: encrypts files locally with AES-128-ECB, hands them to the upload endpoint, and inverts the flow on the download side.
Package cdn talks to the WeChat C2C CDN: encrypts files locally with AES-128-ECB, hands them to the upload endpoint, and inverts the flow on the download side.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL