73 lines
2.1 KiB
Go
73 lines
2.1 KiB
Go
// 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
|
|
}
|