weixin

package
v0.61.1 Latest Latest
Warning

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

Go to latest
Published: May 20, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultAPIBaseURL = "https://ilinkai.weixin.qq.com"
	DefaultCDNBaseURL = "https://novac2c.cdn.weixin.qq.com/c2c"

	SessionExpiredErrCode = -14

	// TypingStatus values for SendTyping.
	TypingStatusTyping = 1
	TypingStatusCancel = 2
)

Variables

View Source
var ErrSessionExpired = errors.New("weixin: session expired (errcode -14)")

ErrSessionExpired is returned by outbound API helpers when the iLink server reports session-expired (ret/errcode == -14). Callers should treat the bot as needing re-login. Test with errors.Is.

Functions

func AESECBPaddedSize added in v0.27.0

func AESECBPaddedSize(plaintextSize int) int

AESECBPaddedSize returns the size after AES-128-ECB encryption with PKCS7 padding. PKCS7 always adds at least 1 byte, so output = ceil((size+1)/16) * 16.

func ClearSession

func ClearSession(sandboxID string)

func DecryptAESECB added in v0.27.0

func DecryptAESECB(ciphertext, key []byte) ([]byte, error)

DecryptAESECB decrypts AES-128-ECB encrypted data with PKCS7 padding.

func DownloadAndDecryptMedia added in v0.27.0

func DownloadAndDecryptMedia(ctx context.Context, cdnBaseURL, encryptQueryParam, aesKeyBase64, fullURL string) ([]byte, error)

DownloadAndDecryptMedia downloads and decrypts a media file from iLink CDN. aesKeyBase64 is the base64-encoded AES key from the CDNMedia.aes_key field. Also supports hex-encoded keys wrapped in base64 (32 hex chars). fullURL, if non-empty, is used directly instead of constructing from encryptQueryParam.

func DownloadFromCDN added in v0.27.0

func DownloadFromCDN(ctx context.Context, cdnBaseURL, encryptQueryParam, fullURL string) ([]byte, error)

DownloadFromCDN downloads a file from the iLink CDN. Prefers fullURL (server-provided) over client-side URL construction from encrypt_query_param. The final URL is gated by parseILinkURL so a malicious server response or stale CDN base cannot redirect downloads to arbitrary hosts.

func EncryptAESECB added in v0.27.0

func EncryptAESECB(plaintext, key []byte) []byte

EncryptAESECB encrypts data with AES-128-ECB and PKCS7 padding.

func ExtractText added in v0.27.0

func ExtractText(msg WeixinMessage) string

ExtractText extracts the text content from a WeixinMessage. Mirrors openclaw-weixin's bodyFromItemList:

  • TEXT items with ref_msg get a "[引用: ...]" prefix (unless the ref is media)
  • VOICE items with speech-to-text use the transcribed text
  • ref_msg content is resolved recursively

func IsSessionExpired added in v0.60.4

func IsSessionExpired(err error) bool

IsSessionExpired reports whether err (or any wrapped err) is ErrSessionExpired.

func NotifyStart added in v0.60.4

func NotifyStart(ctx context.Context, apiBaseURL, botToken string) error

NotifyStart tells the iLink server that this channel client is starting to poll for the given bot, so the server can reconcile per-account online state. Best-effort: failures are non-fatal to the poller lifecycle.

func NotifyStop added in v0.60.4

func NotifyStop(ctx context.Context, apiBaseURL, botToken string) error

NotifyStop tells the iLink server that this channel client is shutting down. Best-effort: failures are logged by the caller but do not affect shutdown.

func SendImageMessage added in v0.27.0

func SendImageMessage(ctx context.Context, apiBaseURL, botToken, toUserID string, encryptQueryParam, aesKeyHex string, ciphertextSize int, contextToken string) error

SendImageMessage sends an image message to a WeChat user via iLink.

func SendTextMessage added in v0.26.0

func SendTextMessage(ctx context.Context, apiBaseURL, botToken, toUserID, text, contextToken string) error

SendTextMessage sends a text message to a WeChat user via iLink.

func SendTyping added in v0.27.0

func SendTyping(ctx context.Context, apiBaseURL, botToken, userID, typingTicket string, status int) error

SendTyping sends a typing indicator to a WeChat user via iLink. status should be TypingStatusTyping (1) or TypingStatusCancel (2).

func SetSession

func SetSession(sandboxID string, s *Session)

func UploadAndSendImage added in v0.27.0

func UploadAndSendImage(ctx context.Context, apiBaseURL, cdnBaseURL, botToken, toUserID string, imageData []byte, contextToken string) error

UploadAndSendImage handles the complete flow: encrypt → CDN upload → send message.

func UploadToCDN added in v0.27.0

func UploadToCDN(ctx context.Context, cdnBaseURL, uploadParam, filekey string, ciphertext []byte, uploadFullURL string) (string, error)

UploadToCDN uploads encrypted file data to the iLink CDN. Returns the download encrypt_query_param from the response header. If uploadFullURL is non-empty, it is used directly as the POST target (takes precedence over uploadParam). The final target URL is gated by parseILinkURL — both uploadFullURL (server-provided) and cdnBaseURL must point at the weixin.qq.com host family or the upload is refused.

Types

type BaseInfo added in v0.26.0

type BaseInfo struct {
	ChannelVersion string `json:"channel_version,omitempty"`
	BotAgent       string `json:"bot_agent,omitempty"`
}

BaseInfo is common metadata attached to every iLink API request.

type CDNMedia added in v0.27.0

type CDNMedia struct {
	EncryptQueryParam string `json:"encrypt_query_param,omitempty"`
	AESKey            string `json:"aes_key,omitempty"`      // base64-encoded
	EncryptType       int    `json:"encrypt_type,omitempty"` // 1 = full packet encryption
	FullURL           string `json:"full_url,omitempty"`     // server-provided download URL (preferred over client-side construction)
}

CDNMedia references a file uploaded to iLink CDN.

type FileItem added in v0.27.0

type FileItem struct {
	Media    *CDNMedia `json:"media,omitempty"`
	FileName string    `json:"file_name,omitempty"`
	Len      string    `json:"len,omitempty"`
}

FileItem holds file attachment metadata.

type GetConfigResponse added in v0.27.0

type GetConfigResponse struct {
	Ret          int    `json:"ret"`
	ErrCode      int    `json:"errcode,omitempty"`
	ErrMsg       string `json:"errmsg,omitempty"`
	TypingTicket string `json:"typing_ticket"`
}

GetConfigResponse is the response from ilink/bot/getconfig.

func GetConfig added in v0.27.0

func GetConfig(ctx context.Context, apiBaseURL, botToken, userID, contextToken string) (*GetConfigResponse, error)

GetConfig fetches bot config for a user, including the typing_ticket needed for SendTyping.

type GetUpdatesRequest added in v0.26.0

type GetUpdatesRequest struct {
	GetUpdatesBuf string   `json:"get_updates_buf"`
	BaseInfo      BaseInfo `json:"base_info"`
}

GetUpdatesRequest is the body for POST /ilink/bot/getupdates.

type GetUpdatesResponse added in v0.26.0

type GetUpdatesResponse struct {
	Ret                  int             `json:"ret"`
	ErrCode              int             `json:"errcode,omitempty"`
	ErrMsg               string          `json:"errmsg,omitempty"`
	Msgs                 []WeixinMessage `json:"msgs,omitempty"`
	GetUpdatesBuf        string          `json:"get_updates_buf,omitempty"`
	LongPollingTimeoutMs int             `json:"longpolling_timeout_ms,omitempty"`
}

GetUpdatesResponse is the response from getupdates.

func GetUpdates added in v0.26.0

func GetUpdates(ctx context.Context, apiBaseURL, botToken, getUpdatesBuf string) (*GetUpdatesResponse, error)

GetUpdates long-polls iLink for new messages. Blocks for up to ~35s. Returns an empty response (Ret=0, no msgs) on client-side timeout.

type GetUploadURLResponse added in v0.27.0

type GetUploadURLResponse struct {
	Ret              int    `json:"ret"`
	ErrCode          int    `json:"errcode,omitempty"`
	ErrMsg           string `json:"errmsg,omitempty"`
	UploadParam      string `json:"upload_param"`
	UploadFullURL    string `json:"upload_full_url,omitempty"`
	ThumbUploadParam string `json:"thumb_upload_param,omitempty"`
}

GetUploadURLResponse is the response from ilink/bot/getuploadurl.

func GetUploadURL added in v0.27.0

func GetUploadURL(ctx context.Context, apiBaseURL, botToken string, params map[string]interface{}) (*GetUploadURLResponse, error)

GetUploadURL obtains a pre-signed CDN upload URL from iLink.

type ImageItem added in v0.27.0

type ImageItem struct {
	Media      *CDNMedia `json:"media,omitempty"`
	ThumbMedia *CDNMedia `json:"thumb_media,omitempty"`
	AESKey     string    `json:"aeskey,omitempty"` // raw AES-128 key as hex string; preferred over media.aes_key for inbound
	URL        string    `json:"url,omitempty"`
	MidSize    int       `json:"mid_size,omitempty"`
	ThumbSize  int       `json:"thumb_size,omitempty"`
	HdSize     int       `json:"hd_size,omitempty"`
}

ImageItem holds the image data for an IMAGE message item.

type MessageItem added in v0.26.0

type MessageItem struct {
	Type      int         `json:"type,omitempty"` // 1=TEXT, 2=IMAGE, 3=VOICE, 4=FILE, 5=VIDEO
	TextItem  *TextItem   `json:"text_item,omitempty"`
	ImageItem *ImageItem  `json:"image_item,omitempty"`
	VoiceItem *VoiceItem  `json:"voice_item,omitempty"`
	FileItem  *FileItem   `json:"file_item,omitempty"`
	VideoItem *VideoItem  `json:"video_item,omitempty"`
	RefMsg    *RefMessage `json:"ref_msg,omitempty"`
}

MessageItem represents a single content item in a WeixinMessage.

type RefMessage added in v0.27.0

type RefMessage struct {
	MessageItem *MessageItem `json:"message_item,omitempty"`
	Title       string       `json:"title,omitempty"`
}

RefMessage holds a quoted/referenced message.

type SendMessageRequest added in v0.26.0

type SendMessageRequest struct {
	Msg      WeixinMessage `json:"msg"`
	BaseInfo BaseInfo      `json:"base_info"`
}

SendMessageRequest is the body for POST /ilink/bot/sendmessage.

type Session

type Session struct {
	QRCode            string // opaque qrcode string for status polling
	QRCodeURL         string // image URL for frontend rendering
	CurrentAPIBaseURL string // host to use for next PollLoginStatus
	StartedAt         time.Time
}

Session holds the state of an in-progress QR login for a single sandbox. CurrentAPIBaseURL tracks the base URL polling should use for the *next* PollLoginStatus call. It starts as the original DefaultAPIBaseURL and may switch mid-flight when the server returns scaned_but_redirect with a new redirect_host (IDC migration).

func GetSession

func GetSession(sandboxID string) *Session

GetSession returns a copy of the active QR-login session for sandboxID, or nil when none is active or the stored one has expired. The returned pointer is detached from the in-memory map: callers may mutate fields (e.g. CurrentAPIBaseURL on IDC redirect) and persist via SetSession without racing concurrent readers of the same map entry.

func StartLogin

func StartLogin(ctx context.Context, apiBaseURL string) (*Session, error)

StartLogin calls the ilink API to generate a new QR code for WeChat login.

Since openclaw-weixin@2.3.1, this is a POST with body {"local_token_list": [...]} so the server can return binded_redirect when the scanning bot is already bound to a known token. We do not currently track previously-issued tokens here, so an empty list is always sent — losing the binded_redirect optimization but matching the current wire protocol.

func TakeSession

func TakeSession(sandboxID string) *Session

TakeSession atomically returns and removes the session (used on confirmed).

type StatusResult

type StatusResult struct {
	Status       string `json:"status"`
	Token        string `json:"bot_token,omitempty"`
	BotID        string `json:"ilink_bot_id,omitempty"`
	BaseURL      string `json:"baseurl,omitempty"`
	UserID       string `json:"ilink_user_id,omitempty"`
	RedirectHost string `json:"redirect_host,omitempty"` // host (no scheme) for scaned_but_redirect
}

StatusResult is the parsed response from get_qrcode_status. The set of statuses matches @tencent-weixin/openclaw-weixin@2.4.3:

wait / scaned / confirmed / expired — original states.
scaned_but_redirect — IDC migration; caller must switch to RedirectHost.
binded_redirect — bot already bound to this instance; no re-login needed.
need_verifycode — server requires a pair-code from the WeChat client.
verify_code_blocked — too many wrong codes; refresh QR and try again.

func PollLoginStatus

func PollLoginStatus(ctx context.Context, apiBaseURL, qrcode string) (*StatusResult, error)

PollLoginStatus long-polls the ilink API for QR code scan status. Blocks for up to ~35 seconds (server-side long-poll).

type TextItem added in v0.26.0

type TextItem struct {
	Text string `json:"text,omitempty"`
}

TextItem holds the text content of a TEXT message item.

type VideoItem added in v0.27.0

type VideoItem struct {
	Media     *CDNMedia `json:"media,omitempty"`
	VideoSize int       `json:"video_size,omitempty"`
}

VideoItem holds video metadata.

type VoiceItem added in v0.27.0

type VoiceItem struct {
	Media      *CDNMedia `json:"media,omitempty"`
	EncodeType int       `json:"encode_type,omitempty"`
	SampleRate int       `json:"sample_rate,omitempty"`
	PlayTime   int       `json:"playtime,omitempty"`
	Text       string    `json:"text,omitempty"` // speech-to-text content
}

VoiceItem holds voice message data.

type WeixinMessage added in v0.26.0

type WeixinMessage struct {
	FromUserID   string        `json:"from_user_id"`
	ToUserID     string        `json:"to_user_id,omitempty"`
	ClientID     string        `json:"client_id,omitempty"`
	CreateTimeMs int64         `json:"create_time_ms,omitempty"`
	MessageType  int           `json:"message_type,omitempty"`  // 1=USER, 2=BOT
	MessageState int           `json:"message_state,omitempty"` // 0=NEW, 1=GENERATING, 2=FINISH
	ItemList     []MessageItem `json:"item_list,omitempty"`
	ContextToken string        `json:"context_token,omitempty"`
}

WeixinMessage is a message from/to iLink.

Jump to

Keyboard shortcuts

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