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 }