package kook // 本文件承载 KOOK 的**入站附件字节内联**(切片 IM-1 / IM-IMG):KOOK 的 s=0 消息把附件 // 地址直接放在 `content` 字段里(图片/视频/文件/音频同理),因此**文件与图片**拿到的是一个 // 绝对 CDN 地址,可以按**同一份来源白名单**直接取回(图片多出来的只是「字节必须是白名单 // 光栅图」与 5 MiB 的图片上限)。 // // 两条边界: // - **只认平台资产主机**:白名单 = KOOK 附件 CDN(`img.kookapp.cn`)+ 已配置的 // API 根(`base_url`)。`content` 字段是发送方可影响的内容(一个伪造的文件消息 // 完全可以填任意字符串),因此**不是**「平台保证的地址」——白名单在这里是硬门: // 不在白名单里,一个字节都不发出去; // - 尺寸/超时/预筛/日志口径全在 `internal/channel/mediafetch`(四个适配器共用)。 import ( "context" "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter" "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/mediafetch" ) // kookAssetOrigin 是 KOOK 附件的 CDN 来源(平台只在它上面提供附件地址)。**文件与图片 // 共用这一条**:图片不另立名单(多一份名单只会多一处漂移),见 media_image_test.go。 const kookAssetOrigin = "https://img.kookapp.cn" // inlineInboundMedia 把受支持格式的附件(**文件类与图片类**)取回并内联到消息上。失败不抛出: // 该附件保持无字节,由 channelbridge 的入站摄取照既有口径给模型一行诚实提示。 func (a *Adapter) inlineInboundMedia(ctx context.Context, msg *adapter.UnifiedMessage) { mediafetch.Inline(ctx, a.log, msg, kookFetcher{a: a}) } // kookFetcher 实现 mediafetch.Fetcher:KOOK 的媒体引用就是 `content` 里的绝对地址。 type kookFetcher struct{ a *Adapter } // Fetch 按来源白名单取回一份 KOOK 附件。 func (f kookFetcher) Fetch(ctx context.Context, m adapter.Media) ([]byte, error) { return mediafetch.Fetch(ctx, mediafetch.Request{ URL: m.URL, Allow: f.a.assetAllowlist(), Client: f.a.client, Timeout: mediafetch.DefaultTimeout, }) } // assetAllowlist 给出 KOOK 附件的来源白名单:附件 CDN + 已配置的 API 根(**文件与图片 // 共用同一份** —— 两者落的是同一批主机,另立一份只会多一处漂移)。第二条是 // 为了让**自建/私有化部署**(把 `base_url` 指到自己的网关)也能走通,且它的可信度与 // 其余 API 调用一致(同一条由部署声明的地址)。两条都读不出来时给空名单 ⇒ 谁都不许。 func (a *Adapter) assetAllowlist() mediafetch.Allowlist { origins := []string{kookAssetOrigin} if origin, err := mediafetch.OriginOf(a.baseURL); err == nil { origins = append(origins, origin) } return mediafetch.NewAllowlist(origins...) }