feat(channels): IM 入站图片采集层(图片白名单 + 微信图片来源 + 复用 vision 内联入口)
- mediafetch 新增**图片白名单**(扩展名 + **字节嗅探兜底**;与文档/音频**三份名单互不污染**),
取回/裁决/上限全部走既有 Fetch(**来源白名单 deny-by-default** + 逐跳重定向校验 + 边读边限 + 超时)
⇒ 没有新造第四份取回实现
- 微信:给图片 URL 配来源白名单(沿用该渠道 CDN 根口径)
- vision 的 `data:` URI 内联入口为**只读复用**(未改 vision 一行)
- **行为变更**:图片现在**会**被尝试取回(**字节嗅探是唯一裁决权** —— 不是图片字节就不写 `Data`);
其它渠道尚无图片来源白名单 ⇒ 由 deny-by-default 拒绝(**安全**,只是暂读不到,走诚实降级)——
故同步更新了 kook 的既有测试(原断言「图片保持既有路径 = 不取回」已随口径演进)
- ⚠️ **`channelbridge` 一个字没碰**(接线点在切片报告 §7),等监工接线 + 补其它渠道白名单
- 测试:3 个新文件;变异 5 条
This commit is contained in:
1 parent
5de708a560
commit
6a545badbd
8 files changed
+1004
-37
No files matched your search
@@ -7,6 +7,8 @@ package mediafetch
|
||||
//
|
||||
// 语音/音频附件由同目录的 `inline_voice.go` 承载(切片 IM-ASR):它复用这里的取回
|
||||
// 与日志口径,但用一份**独立**的音频白名单(不得污染 `documents.ExtAllowed`)。
|
||||
// 图片附件同理由 `inline_image.go` 承载(切片 IM-IMG):独立的图片白名单 + 图片口径的
|
||||
// 上限(5 MiB)+ 字节嗅探裁决,与上面两份名单互不污染。
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -33,16 +35,18 @@ type Fetcher interface {
|
||||
Fetch(ctx context.Context, media adapter.Media) ([]byte, error)
|
||||
}
|
||||
|
||||
// Inline 把消息里**文件类**与**语音类**媒体在受支持格式与限额内的字节内联进
|
||||
// `Media.Data`,使下游(channelbridge 的入站摄取 / 语音归一)拿到字节。它是四个适配器
|
||||
// 共享的唯一实现:
|
||||
// Inline 把消息里**文件类**、**图片类**与**语音类**媒体在受支持格式与限额内的字节
|
||||
// 内联进 `Media.Data`,使下游(channelbridge 的入站摄取 / 语音归一 / 图片内联成
|
||||
// data: URI)拿到字节。它是四个适配器共享的唯一实现:
|
||||
//
|
||||
// - 文件类走 `inlineFiles`(图片仍走各自既有链路:图片进视觉链路,design/24 §4.2 末);
|
||||
// - 文件类走 `inlineFiles`(`documents.ExtAllowed` 白名单,20 MiB);
|
||||
// - 图片类走 `inlineImages`(切片 IM-IMG,`inline_image.go`):它用一份**独立的
|
||||
// 图片白名单**与**图片口径的上限**(5 MiB),并以字节嗅探为最终裁决;
|
||||
// - 语音类走 `inlineVoices`(切片 IM-ASR,design/06 §1.1、design/08 §4.1):它用一份
|
||||
// **独立的音频白名单**,绝不与 `documents.ExtAllowed` 混淆——后者是 Web 文档上传的
|
||||
// 口径,往里加音频格式会污染文档白名单;
|
||||
// - 两条分支共用同一份取回口径(来源白名单、逐跳重定向校验、边读边限、超时、日志只记
|
||||
// 来源不记完整 URL),因此四个适配器零改动即可拿到语音字节。
|
||||
// - 三条分支共用同一份取回口径(来源白名单、逐跳重定向校验、边读边限、超时、日志只记
|
||||
// 来源不记完整 URL),因此四个适配器零改动即可拿到图片与语音字节。
|
||||
//
|
||||
// 它**不返回错误**:入站附件的失败由「留空字节 + 日志 + 下游诚实提示」表达,绝不
|
||||
// 让一条读不到的附件拖垮整个回合(失败不炸)。
|
||||
@@ -51,9 +55,32 @@ func Inline(ctx context.Context, log *slog.Logger, msg *adapter.UnifiedMessage,
|
||||
return
|
||||
}
|
||||
inlineFiles(ctx, log, msg, f)
|
||||
inlineImages(ctx, log, msg, f)
|
||||
inlineVoices(ctx, log, msg, f)
|
||||
}
|
||||
|
||||
// MaxBytesFor 返回一条媒体取回时该用的**字节上限**,按类型分档:图片 5 MiB
|
||||
// (`MaxImageBytes`)、语音 5 MiB(`MaxVoiceBytes`)、其余(文档类附件)20 MiB
|
||||
// (`documents.DefaultMaxBytes`)。
|
||||
//
|
||||
// 它给各适配器的 Fetcher 用(`mediafetch.Request.MaxBytes`):取回发生在 Fetcher 里,
|
||||
// 而「同一份取回口径服务三条链路」意味着上限不能由取回层替调用方猜 —— 这一份映射是
|
||||
// 唯一出处,渠道侧只需 `MaxBytes: mediafetch.MaxBytesFor(m)`。
|
||||
//
|
||||
// `Kind` 为空的媒体(只给了消息级内容类型的形状)无法在这一层判出类型,回落到文档
|
||||
// 上限;此时图片仍由 `inlineImages` 的**取回后**复核(> MaxImageBytes 一律不内联)
|
||||
// 兜住,因此不会出现「巨图进了上下文」。
|
||||
func MaxBytesFor(m adapter.Media) int64 {
|
||||
switch m.Kind {
|
||||
case adapter.MediaImage:
|
||||
return MaxImageBytes
|
||||
case adapter.MediaVoice:
|
||||
return MaxVoiceBytes
|
||||
default:
|
||||
return documents.DefaultMaxBytes
|
||||
}
|
||||
}
|
||||
|
||||
// inlineFiles 内联**文件类**媒体:
|
||||
//
|
||||
// - 先按平台给的文件名**预筛扩展名**(`documents.ExtAllowed`,全仓唯一名单):
|
||||
|
||||
Reference in new issue
Block a user