chore: checkpoint 第一版
ci / verify (push) Canceled after 0s

This commit is contained in:
Codex Local
2026-09-06 17:04:11 +08:00
parent a8cdec98d1
commit 0c2151ace9
659 changed files with 185855 additions and 554 deletions
+72
View File
@@ -0,0 +1,72 @@
// Package volcsubtitle implements the native RTS subtitle callback documented at
// https://docs.volcengine.com/docs/6348/1337284 (verified 2026-09-06).
package volcsubtitle
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"unicode/utf8"
)
var ErrInvalid = errors.New("invalid native subtitle packet")
type Envelope struct {
Message string `json:"message"`
Signature string `json:"signature"`
}
type Segment struct {
UserID string `json:"userId"`
RoundID *int64 `json:"roundId"`
Sequence *int64 `json:"sequence"`
Text string `json:"text"`
Definite bool `json:"definite"`
Paragraph bool `json:"paragraph"`
}
type Packet struct {
Type string `json:"type"`
Data []Segment `json:"data"`
DeviceName string `json:"deviceName,omitempty"`
ProductKey string `json:"productKey,omitempty"`
}
// Bind authentication to the platform conversation. A delayed callback can
// never be reassigned by looking up the device's newest active conversation.
func Signature(secret []byte, conversationID string) string {
mac := hmac.New(sha256.New, secret)
mac.Write([]byte("volc-subtitle-v1\n" + conversationID))
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}
func Decode(message string) (Packet, error) {
var p Packet
if len(message) > 350000 {
return p, ErrInvalid
}
data, err := base64.StdEncoding.DecodeString(message)
if err != nil || len(data) == 0 {
return p, ErrInvalid
}
if data[0] != '{' {
if len(data) < 8 || string(data[:4]) != "subv" || uint64(binary.BigEndian.Uint32(data[4:8])) != uint64(len(data)-8) {
return p, ErrInvalid
}
data = data[8:]
}
if !utf8.Valid(data) {
return p, ErrInvalid
}
if json.Unmarshal(data, &p) != nil || p.Type != "subtitle" || len(p.Data) == 0 || len(p.Data) > 100 || len(p.DeviceName) > 128 || len(p.ProductKey) > 200 {
return Packet{}, ErrInvalid
}
for _, s := range p.Data {
if s.UserID == "" || len(s.UserID) > 200 || s.RoundID == nil || s.Sequence == nil ||
*s.RoundID < 0 || *s.Sequence < 0 || len([]rune(s.Text)) > 20000 {
return Packet{}, ErrInvalid
}
}
return p, nil
}
+53
View File
@@ -0,0 +1,53 @@
package volcsubtitle
import (
"encoding/base64"
"encoding/binary"
"testing"
)
func frame(body string) string {
b := append(make([]byte, 8), []byte(body)...)
copy(b, "subv")
binary.BigEndian.PutUint32(b[4:8], uint32(len(b)-8))
return base64.StdEncoding.EncodeToString(b)
}
func TestDecodeNativeSubtitle(t *testing.T) {
valid := `{"type":"subtitle","data":[{"userId":"device-a","roundId":0,"sequence":0,"definite":true,"paragraph":false,"text":"你好"},{"userId":"bot-a","roundId":0,"sequence":1,"definite":true,"paragraph":true,"text":"下午好"}]}`
p, err := Decode(frame(valid))
if err != nil || len(p.Data) != 2 || p.Data[0].Paragraph || p.Data[0].Text != "你好" {
t.Fatalf("%+v %v", p, err)
}
for _, s := range []string{"", "not base64!", base64.StdEncoding.EncodeToString([]byte("subv")), frame(`{"type":"other","data":[]}`), frame(`{"type":"subtitle","data":[{"userId":"a","sequence":0}]}`), frame(`{"type":"subtitle","data":[{"userId":"a","roundId":0,"sequence":-1}]}`)} {
if _, err := Decode(s); err == nil {
t.Errorf("accepted malformed packet %q", s)
}
}
decoded, _ := base64.StdEncoding.DecodeString(frame(valid))
decoded[7]++
if _, err := Decode(base64.StdEncoding.EncodeToString(decoded)); err == nil {
t.Fatal("accepted incorrect binary length")
}
}
func TestSignatureIsConversationScoped(t *testing.T) {
secret := []byte("dedicated-test-secret-32-bytes-long")
if Signature(secret, "a") == Signature(secret, "b") || Signature(secret, "a") == Signature([]byte("other"), "a") {
t.Fatal("signature must bind both secret and conversation")
}
}
func TestHardwareJSONSubtitle(t *testing.T) {
for _, body := range []string{
`{"type":"subtitle","deviceName":"test-device","productKey":"test-product","data":[{"userId":"input","roundId":1,"sequence":0,"text":"测试","definite":true}]}`,
`{"type":"subtitle","deviceName":"test-device","productKey":"test-product","data":[{"userId":"output","roundId":1,"sequence":1,"text":"","definite":true,"paragraph":true}]}`,
} {
for _, encoded := range []string{base64.StdEncoding.EncodeToString([]byte(body)), frame(body)} {
p, err := Decode(encoded)
if err != nil || p.DeviceName != "test-device" || p.ProductKey != "test-product" {
t.Fatalf("hardware packet=%+v err=%v", p, err)
}
}
}
}