2025-03-26 18:49:42 -06:00

332 lines
8.0 KiB
Go

package twt_avro
import (
"io"
"time"
"github.com/hamba/avro"
"go.yarn.social/lextwt"
"go.yarn.social/types"
)
const (
keySubject = "social.yarn.lextwt.subject"
keyCode = "social.yarn.lextwt.code"
keyMention = "social.yarn.lextwt.mention"
keyHashtag = "social.yarn.lextwt.hashtag"
keyLink = "social.yarn.lextwt.link"
keyBangmention = "social.yarn.lextwt.bangmention"
keyLinesep = "social.yarn.lextwt.linesep"
keyComment = "social.yarn.lextwt.comment"
keyText = "social.yarn.lextwt.text"
keyTwt = "social.yarn.lextwt.twt"
keyTwter = "social.yarn.lextwt.twter"
keyRegistry = "social.yarn.lextwt.registry"
keyFeed = "social.yarn.lextwt.feed"
)
func Register() {
avro.Register(keyText, "")
avro.Register(keySubject, Subject{})
avro.Register(keyCode, Code{})
avro.Register(keyMention, Mention{})
avro.Register(keyHashtag, Hashtag{})
avro.Register(keyLink, Link{})
avro.Register(keyBangmention, Bangmention{})
avro.Register(keyLinesep, Linesep{})
avro.Register(keyComment, Comment{})
avro.Register(keyTwt, Twt{})
avro.Register(keyTwter, Twter{})
avro.Register(keyRegistry, Registry{})
avro.Register(keyFeed, Feed{})
}
func Elem(o any) any {
key := "unknown"
switch o.(type) {
case Subject:
key = keySubject
case Code:
key = keyCode
case Mention:
key = keyMention
case Hashtag:
key = keyHashtag
case Link:
key = keyLink
case Bangmention:
key = keyBangmention
case Linesep:
key = keyLinesep
case Comment:
key = keyComment
case string:
return o
}
return map[string]any{key: o}
}
func Msg(items ...any) []any {
return items
}
func FromTwt(twt types.Twt) Twt {
return FromLextwt(twt.(*lextwt.Twt))
}
func FromLextwt(twt *lextwt.Twt) Twt {
if twt == nil {
return Twt{}
}
ts := twt.Created()
zone, offset := ts.Zone()
l := Twt{
Nick: twt.Twter().Nick,
URI: twt.Twter().URI,
Created: ts.UnixMilli(),
CreatedOffset: offset,
CreatedZone: zone,
}
for _, e := range twt.Elems() {
if e == nil {
continue
}
if e == lextwt.LineSeparator {
l.Msg = append(l.Msg, Elem(Linesep{}))
continue
}
switch e := e.(type) {
case *lextwt.Subject:
l.Msg = append(l.Msg, Elem(Subject{Subject: e.Subject(), Tag: e.Tag().Text(), Target: e.Tag().Target()}))
case *lextwt.Code:
l.Msg = append(l.Msg, Elem(Code{Code: e.Text(), Codetype: int(e.CodeType())}))
case *lextwt.Mention:
l.Msg = append(l.Msg, Elem(Mention{Name: e.Name(), Domain: e.Domain(), Target: e.Target()}))
case *lextwt.Tag:
l.Msg = append(l.Msg, Elem(Hashtag{Tag: e.Text(), Target: e.Target()}))
case *lextwt.Link:
l.Msg = append(l.Msg, Elem(Link{LinkType: int(e.LinkType()), Text: e.Text(), Target: e.Target(), Title: e.Title()}))
case *lextwt.BangMention:
l.Msg = append(l.Msg, Elem(Bangmention{Name: e.Name(), Target: e.Target()}))
case *lextwt.Comment:
l.Msg = append(l.Msg, Elem(Comment{Comment: e.Text(), Key: e.Key(), Value: e.Value()}))
case *lextwt.Text:
l.Msg = append(l.Msg, Elem(e.Literal()))
}
}
return l
}
func (l Twt) ToTwt() types.Twt {
return l.ToLextwt()
}
func (lx Twt) ToLextwt() *lextwt.Twt {
twter := types.Twter{
Nick: lx.Nick,
URI: lx.URI,
}
ts := time.UnixMilli(lx.Created)
if tz := time.FixedZone(lx.CreatedZone, lx.CreatedOffset); tz != nil {
ts = ts.In(tz)
}
dt := lextwt.NewDateTime(ts, ts.Format(time.RFC3339))
elems := make([]lextwt.Elem, 0, len(lx.Msg))
for _, e := range lx.Msg {
switch e := e.(type) {
case map[string]any:
if text, ok := e["string"].(string); ok {
elems = append(elems, lextwt.NewText(text))
}
if e, ok := e[keySubject].(map[string]any); ok {
subject := read[string](e, "subject")
tag := read[string](e, "tag")
target := read[string](e, "target")
if subject == "" {
elems = append(elems, lextwt.NewSubjectTag(tag, target))
} else {
elems = append(elems, lextwt.NewSubject(subject))
}
}
if m, ok := e[keyCode].(map[string]any); ok {
code := read[string](m, "code")
codeType := lextwt.CodeType(read[int8](m, "codetype"))
elems = append(elems, lextwt.NewCode(code, codeType))
}
if e, ok := e[keyMention].(map[string]any); ok {
name := read[string](e, "name")
target := read[string](e, "target")
elems = append(elems, lextwt.NewMention(name, target))
}
if e, ok := e[keyHashtag].(map[string]any); ok {
tag := read[string](e, "tag")
target := read[string](e, "target")
elems = append(elems, lextwt.NewTag(tag, target))
}
if e, ok := e[keyLink].(map[string]any); ok {
text := read[string](e, "text")
target := read[string](e, "target")
linkType := lextwt.LinkType(read[int](e, "linkType"))
elems = append(elems, lextwt.NewLink(text, target, linkType))
}
if e, ok := e[keyBangmention].(map[string]any); ok {
name := read[string](e, "name")
target := read[string](e, "target")
elems = append(elems, lextwt.NewBangMention(name, target))
}
if e, ok := e[keyComment].(map[string]any); ok {
comment := read[string](e, "comment")
key := read[string](e, "key")
value := read[string](e, "value")
if key != "" {
elems = append(elems, lextwt.NewCommentValue(comment, key, value))
} else {
elems = append(elems, lextwt.NewComment(comment))
}
}
if _, ok := e[keyLinesep].(map[string]any); ok {
elems = append(elems, lextwt.LineSeparator)
}
}
}
return lextwt.NewTwt(twter, dt, elems...)
}
func read[T any](m map[string]any, k string) T {
val, ok := m[k].(T)
if !ok {
var zero T
return zero
}
return val
}
type TwtRegistry interface {
Twters() []*types.Twter
Preamble() lextwt.Comments
Twts() types.Twts
WriteTo(w io.Writer) (int64, error)
}
func EncodeRegistry(r TwtRegistry) ([]byte, error) {
out := Registry{}
for _, comment := range r.Preamble() {
if comment.Key() != "" {
out.Preamble = append(out.Preamble, Comment{Key: comment.Key(), Value: comment.Value()})
continue
}
out.Preamble = append(out.Preamble, Comment{Comment: comment.Text()})
}
for _, twt := range r.Twts() {
out.Twts = append(out.Twts, FromTwt(twt))
}
return out.Marshal()
}
func DecodeRegistry(b []byte) (TwtRegistry, error) {
var out Registry
if err := out.Unmarshal(b); err != nil {
return nil, err
}
preamble := make(lextwt.Comments, 0, len(out.Preamble))
for _, comment := range out.Preamble {
if comment.Key != "" {
preamble = append(preamble, lextwt.NewCommentValue(comment.Comment, comment.Key, comment.Value))
} else {
preamble = append(preamble, lextwt.NewComment(comment.Comment))
}
}
twts := make(types.Twts, 0, len(out.Twts))
for _, twt := range out.Twts {
twts = append(twts, twt.ToTwt())
}
return lextwt.NewTwtRegistry(preamble, twts), nil
}
type TwtFeed interface {
Twter() *types.Twter
Preamble() lextwt.Comments
Twts() types.Twts
WriteTo(w io.Writer) (int64, error)
}
func EncodeFeed(r TwtFeed) ([]byte, error) {
out := Feed{}
out.Twter.Nick = r.Twter().Nick
out.Twter.URI = r.Twter().URI
for _, comment := range r.Preamble() {
if comment.Key() != "" {
out.Preamble = append(out.Preamble, Comment{Key: comment.Key(), Value: comment.Value()})
continue
}
out.Preamble = append(out.Preamble, Comment{Comment: comment.Text()})
}
for _, twt := range r.Twts() {
out.Twts = append(out.Twts, FromTwt(twt))
}
return out.Marshal()
}
func DecodeFeed(b []byte) (TwtFeed, error) {
var out Feed
if err := out.Unmarshal(b); err != nil {
return nil, err
}
twter := types.Twter{
Nick: out.Twter.Nick,
URI: out.Twter.URI,
}
preamble := make(lextwt.Comments, 0, len(out.Preamble))
for _, comment := range out.Preamble {
if comment.Key != "" {
preamble = append(preamble, lextwt.NewCommentValue(comment.Comment, comment.Key, comment.Value))
} else {
preamble = append(preamble, lextwt.NewComment(comment.Comment))
}
}
twts := make(types.Twts, 0, len(out.Twts))
for _, twt := range out.Twts {
twts = append(twts, twt.ToTwt())
}
return lextwt.NewTwtFile(twter, preamble, twts), nil
}