docs(comments): 注释中文化 · plugins/channels/weixin_ilink(20 文件,纯替换)
- 只翻注释/文档字符串:剥掉行尾注释后,每条「-」都有逐字节相同的「+」对应行(8 组,代码零改动) - 保留协议/线上标识符原文:errcode、bot_token、context_token、getupdates、binded_redirect、PKCS#7、ECB 等 - 术语对齐既有译法:owner→主人、peer→对端、long poll→长轮询、backoff→退避、sentinel error→哨兵错误 - 已知既有欠债(未顺手修):login_reuse_test.go 在 HEAD 上就未 gofmt 干净;login_test.go 的 TestStartLogin_ScanStatusTransition 91 行 >80(HEAD 同为 91 行)
This commit is contained in:
1 parent
09fba77f87
commit
95f97f5df3
20 files changed
+488
-590
No files matched your search
@@ -16,19 +16,18 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||||
)
|
||||
|
||||
// item kind constants reuse the iLink item type codes from poll.go, so
|
||||
// outbound media maps to the same numbers as inbound parsing.
|
||||
// item 类型常量复用 poll.go 里的 iLink item 类型码,使出站媒体与入站解析映射到
|
||||
// 同一组数字。
|
||||
|
||||
// sendResult is the sendmessage response envelope.
|
||||
// sendResult 是 sendmessage 的响应外壳。
|
||||
type sendResult struct {
|
||||
Ret int `json:"ret"`
|
||||
Errcode int `json:"errcode"`
|
||||
}
|
||||
|
||||
// SendMessage delivers a reply to the channel. Each message is sent to the
|
||||
// peer resolved from the message (or resp.ReplyTo) using that peer's stored
|
||||
// context_token, so the platform can associate the reply with the right
|
||||
// conversation (design/08 §8).
|
||||
// SendMessage 向渠道投递一条回复。每条消息都发给从该消息(或 resp.ReplyTo)
|
||||
// 解析出的对端,并使用该对端已存的 context_token,以便平台把回复关联到正确的
|
||||
// 会话(design/08 §8)。
|
||||
func (a *Adapter) SendMessage(ctx context.Context, resp adapter.UnifiedResponse) error {
|
||||
if err := a.sendGuard(); err != nil {
|
||||
return err
|
||||
@@ -45,11 +44,10 @@ func (a *Adapter) SendMessage(ctx context.Context, resp adapter.UnifiedResponse)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendProactive pushes a message that was not triggered by a recent inbound.
|
||||
// WeChat iLink only allows this inside the 24h window since the user's last
|
||||
// message (design/08 §3-4): when the user has been silent longer, or has never
|
||||
// messaged, the push is rejected with ErrProactiveWindow and Capabilities.
|
||||
// Proactive is only true so callers know the capability exists.
|
||||
// SendProactive 推送一条并非由近期入站消息触发的消息。微信 iLink 只允许在用户
|
||||
// 最后一条消息之后的 24 小时窗口内这么做(design/08 §3-4):用户沉默更久、或从未
|
||||
// 发过消息时,推送以 ErrProactiveWindow 被拒,而 Capabilities.Proactive 仅为 true,
|
||||
// 好让调用方知道该能力存在。
|
||||
func (a *Adapter) SendProactive(ctx context.Context, msg adapter.UnifiedMessage) error {
|
||||
if err := a.sendGuard(); err != nil {
|
||||
return err
|
||||
@@ -73,9 +71,9 @@ func (a *Adapter) SendProactive(ctx context.Context, msg adapter.UnifiedMessage)
|
||||
return a.sendToOne(ctx, peer, msg)
|
||||
}
|
||||
|
||||
// sendGuard reports the error a send must surface before touching the network: a
|
||||
// session iLink already rejected (errcode=-14) reads as ErrSessionRejected — the
|
||||
// owner must re-scan — while any other down session stays ErrNotConnected.
|
||||
// sendGuard 报告发送在触网之前必须暴露的错误:iLink 已拒绝的会话(errcode=-14)
|
||||
// 表现为 ErrSessionRejected——主人必须重新扫码——而其余任何断开状态仍是
|
||||
// ErrNotConnected。
|
||||
func (a *Adapter) sendGuard() error {
|
||||
if rejected, reason := a.rejectedState(); rejected {
|
||||
return fmt.Errorf("weixin_ilink: send: %w: %s", ErrSessionRejected, reason)
|
||||
@@ -86,8 +84,8 @@ func (a *Adapter) sendGuard() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendToOne sends one normalized message to a peer as a native iLink message,
|
||||
// composing its item_list from any text plus uploaded media.
|
||||
// sendToOne 把一条归一化消息作为原生 iLink 消息发给某个对端,用文本加上已上传的
|
||||
// 媒体组装其 item_list。
|
||||
func (a *Adapter) sendToOne(ctx context.Context, peer string, m adapter.UnifiedMessage) error {
|
||||
if peer == "" {
|
||||
return ErrNoContextToken
|
||||
@@ -114,8 +112,8 @@ func (a *Adapter) sendToOne(ctx context.Context, peer string, m adapter.UnifiedM
|
||||
return a.sendNative(ctx, peer, ctxToken, items)
|
||||
}
|
||||
|
||||
// buildMediaItem encrypts media bytes, uploads them to the CDN and returns the
|
||||
// native item referencing the uploaded object and its AES key.
|
||||
// buildMediaItem 加密媒体字节、上传到 CDN,并返回引用该已上传对象及其 AES 密钥的
|
||||
// 原生 item。
|
||||
func (a *Adapter) buildMediaItem(ctx context.Context, md adapter.Media) (any, error) {
|
||||
plaintext := md.Data
|
||||
if len(plaintext) == 0 && md.URL != "" {
|
||||
@@ -144,14 +142,14 @@ func (a *Adapter) buildMediaItem(ctx context.Context, md adapter.Media) (any, er
|
||||
return nativeMediaItem(nativeMediaTypeFor(md.Kind), uploadURL, key, md), nil
|
||||
}
|
||||
|
||||
// sendNative posts a composed sendmessage payload.
|
||||
// sendNative 提交一份组装好的 sendmessage 载荷。
|
||||
func (a *Adapter) sendNative(ctx context.Context, peer, ctxToken string, items []any) error {
|
||||
body, err := json.Marshal(map[string]any{
|
||||
"msg": map[string]any{
|
||||
"to_user_id": peer,
|
||||
"client_id": newClientID(),
|
||||
"message_type": 2, // bot message
|
||||
"message_state": 2, // FINISH
|
||||
"message_type": 2, // 机器人消息
|
||||
"message_state": 2, // FINISH(结束态)
|
||||
"context_token": ctxToken,
|
||||
"item_list": items,
|
||||
},
|
||||
@@ -165,9 +163,8 @@ func (a *Adapter) sendNative(ctx context.Context, peer, ctxToken string, items [
|
||||
return fmt.Errorf("weixin_ilink: sendmessage: %w", err)
|
||||
}
|
||||
if out.Errcode == errcodeSessionRejected {
|
||||
// The send path sees the same rejected credential as the poll path: record
|
||||
// the rejected state so the adapter stops claiming a live session and the
|
||||
// panel can ask for a re-scan.
|
||||
// 发送路径与轮询路径看到的是同一份被拒凭据:记录该拒绝状态,让适配器不再
|
||||
// 宣称会话存活,面板也就可以要求重新扫码。
|
||||
a.markRejected(rescanReasonSendMessage)
|
||||
return ErrSessionRejected
|
||||
}
|
||||
@@ -177,8 +174,8 @@ func (a *Adapter) sendNative(ctx context.Context, peer, ctxToken string, items [
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUploadURL requests a pre-signed CDN upload URL. The response field names
|
||||
// vary across server versions, so several candidates are tried.
|
||||
// getUploadURL 申请一条预签名的 CDN 上传 URL。响应字段名在不同服务端版本间有
|
||||
// 差异,所以会尝试多个候选键。
|
||||
func (a *Adapter) getUploadURL(ctx context.Context) (string, error) {
|
||||
body, err := json.Marshal(map[string]any{})
|
||||
if err != nil {
|
||||
@@ -190,8 +187,8 @@ func (a *Adapter) getUploadURL(ctx context.Context) (string, error) {
|
||||
return "", fmt.Errorf("weixin_ilink: getuploadurl: %w", err)
|
||||
}
|
||||
if intOf(raw["errcode"]) == errcodeSessionRejected {
|
||||
// Media upload is part of the send path, so the same rejected credential is
|
||||
// recorded here too rather than degrading into "no url returned".
|
||||
// 媒体上传属于发送路径,所以这里同样记录这份被拒凭据,而不是退化成
|
||||
//「未返回 url」。
|
||||
a.markRejected(rescanReasonUploadURL)
|
||||
return "", ErrSessionRejected
|
||||
}
|
||||
@@ -202,7 +199,7 @@ func (a *Adapter) getUploadURL(ctx context.Context) (string, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// putBytes uploads encrypted media bytes to a pre-signed CDN URL.
|
||||
// putBytes 把加密后的媒体字节上传到预签名 CDN URL。
|
||||
func (a *Adapter) putBytes(ctx context.Context, uploadURL string, data []byte) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadURL, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
@@ -220,7 +217,7 @@ func (a *Adapter) putBytes(ctx context.Context, uploadURL string, data []byte) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// nativeTextItem builds a text item_list entry.
|
||||
// nativeTextItem 构造一个文本 item_list 条目。
|
||||
func nativeTextItem(text string) map[string]any {
|
||||
return map[string]any{
|
||||
"type": itemText,
|
||||
@@ -228,8 +225,8 @@ func nativeTextItem(text string) map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
// nativeMediaItem builds a media item_list entry that references the uploaded
|
||||
// object. Both cdn_ref and url are set because the exact field is not public.
|
||||
// nativeMediaItem 构造一个引用已上传对象的媒体 item_list 条目。同时设置 cdn_ref
|
||||
// 与 url,因为确切的字段名并未公开。
|
||||
func nativeMediaItem(kind int, ref string, key []byte, md adapter.Media) map[string]any {
|
||||
item := map[string]any{
|
||||
"type": kind,
|
||||
@@ -245,7 +242,7 @@ func nativeMediaItem(kind int, ref string, key []byte, md adapter.Media) map[str
|
||||
return item
|
||||
}
|
||||
|
||||
// nativeMediaTypeFor maps a normalized MediaKind to the iLink item type code.
|
||||
// nativeMediaTypeFor 把归一化的 MediaKind 映射到 iLink item 类型码。
|
||||
func nativeMediaTypeFor(kind adapter.MediaKind) int {
|
||||
switch kind {
|
||||
case adapter.MediaImage:
|
||||
@@ -259,8 +256,8 @@ func nativeMediaTypeFor(kind adapter.MediaKind) int {
|
||||
}
|
||||
}
|
||||
|
||||
// newClientID returns a random hex client id used to de-duplicate sends. On an
|
||||
// (unlikely) crypto/rand failure it falls back to a time-based value.
|
||||
// newClientID 返回随机的十六进制 client id,用于发送去重。若(极小概率的)
|
||||
// crypto/rand 失败,则退回到基于时间的值。
|
||||
func newClientID() string {
|
||||
var b [12]byte
|
||||
if _, err := rand.Read(b[:]); err == nil {
|
||||
|
||||
Reference in new issue
Block a user