EagleCast
publish-github-pages / deploy (push) Waiting to run

This commit is contained in:
h202-wq
2026-07-09 10:03:32 -04:00
commit 66d9a033c9
446 changed files with 162542 additions and 0 deletions
+257
View File
@@ -0,0 +1,257 @@
package email
import (
"crypto/tls"
"fmt"
"math/rand"
"net/mail"
"net/smtp"
"net/textproto"
"strings"
"source.offmarket.win/aleagle/eaglecast/internal/utils"
"source.offmarket.win/aleagle/eaglecast/models"
"github.com/knadh/smtppool/v2"
)
const (
MessengerName = "email"
hdrReturnPath = "Return-Path"
hdrBcc = "Bcc"
hdrCc = "Cc"
hdrMessageID = "Message-Id"
)
// Server represents an SMTP server's credentials.
type Server struct {
// Name is a unique identifier for the server.
Name string `json:"name"`
Username string `json:"username"`
Password string `json:"password"`
AuthProtocol string `json:"auth_protocol"`
TLSType string `json:"tls_type"`
TLSSkipVerify bool `json:"tls_skip_verify"`
EmailHeaders map[string]string `json:"email_headers"`
FromAddresses []string `json:"from_addresses"`
// Rest of the options are embedded directly from the smtppool lib.
// The JSON tag is for config unmarshal to work.
//lint:ignore SA5008 ,squash is needed by koanf/mapstructure config unmarshal.
smtppool.Opt `json:",squash"`
pool *smtppool.Pool
}
// Emailer is the SMTP e-mail messenger.
type Emailer struct {
name string
// pools holds groups of SMTP servers indexed by a key ('from'-address
// or a domain set per SMTPs server). An empty key holds all servers
// and is the fallback round-robin when there's no match (old behaviour).
pools map[string][]*Server
}
// NormalizeAddr normalizes an e-mail address (strip spaces, lowercase).
func NormalizeAddr(s string) string {
return strings.ToLower(strings.TrimSpace(s))
}
// New returns an SMTP e-mail Messenger backend with the given SMTP servers.
// Group indicates whether the messenger represents a group of SMTP servers (1 or more)
// that are used as a round-robin pool, or a single server.
func New(name string, servers ...Server) (*Emailer, error) {
e := &Emailer{
name: name,
pools: make(map[string][]*Server),
}
for _, srv := range servers {
s := srv
var auth smtp.Auth
switch s.AuthProtocol {
case "cram":
auth = smtp.CRAMMD5Auth(s.Username, s.Password)
case "plain":
auth = smtp.PlainAuth("", s.Username, s.Password, s.Host)
case "login":
auth = &smtppool.LoginAuth{Username: s.Username, Password: s.Password}
case "", "none":
default:
return nil, fmt.Errorf("unknown SMTP auth type '%s'", s.AuthProtocol)
}
s.Opt.Auth = auth
// TLS config.
s.Opt.SSL = smtppool.SSLNone
if s.TLSType != "none" {
s.TLSConfig = &tls.Config{}
if s.TLSSkipVerify {
s.TLSConfig.InsecureSkipVerify = s.TLSSkipVerify
} else {
s.TLSConfig.ServerName = s.Host
}
// SSL/TLS, not STARTTLS.
switch s.TLSType {
case "TLS":
s.Opt.SSL = smtppool.SSLTLS
case "STARTTLS":
s.Opt.SSL = smtppool.SSLSTARTTLS
}
}
pool, err := smtppool.New(s.Opt)
if err != nil {
return nil, err
}
s.pool = pool
// Add to the global list (empty key) and to each from-address
// bucket. Duplicate keys across servers are fine and get round-robin'd.
e.pools[""] = append(e.pools[""], &s)
for _, addr := range s.FromAddresses {
if key := NormalizeAddr(addr); key != "" {
e.pools[key] = append(e.pools[key], &s)
}
}
}
return e, nil
}
// Name returns the messenger's name.
func (e *Emailer) Name() string {
return e.name
}
// Push pushes a message to the server.
func (e *Emailer) Push(m models.Message) error {
// Pick the from-address-routed pool if there is one, else default
// to the full pool (empty key) for roundrobin.
pool := e.pools[""]
if len(e.pools) > 1 {
if srvs := e.getPool(m.From); srvs != nil {
pool = srvs
}
}
srv := pool[rand.Intn(len(pool))]
// Are there attachments?
var files []smtppool.Attachment
if m.Attachments != nil {
files = make([]smtppool.Attachment, 0, len(m.Attachments))
for _, f := range m.Attachments {
a := smtppool.Attachment{
Filename: f.Name,
Header: f.Header,
Content: make([]byte, len(f.Content)),
HTMLRelated: f.IsInline,
}
copy(a.Content, f.Content)
files = append(files, a)
}
}
// Create the email.
em := smtppool.Email{
From: m.From,
To: m.To,
Subject: m.Subject,
Attachments: files,
}
em.Headers = textproto.MIMEHeader{}
// Attach SMTP level headers.
for k, v := range srv.EmailHeaders {
em.Headers.Set(k, v)
}
// Attach e-mail level headers.
for k, v := range m.Headers {
em.Headers.Set(k, v[0])
}
// Generate Message-Id based on the From address.
if em.Headers.Get(hdrMessageID) == "" {
d := "localhost"
if a, err := mail.ParseAddress(m.From); err == nil {
d = a.Address[strings.LastIndex(a.Address, "@")+1:]
}
if r, err := utils.GenerateRandomString(24); err == nil {
em.Headers.Set(hdrMessageID, fmt.Sprintf("<%s@%s>", r, d))
}
}
// If the `Return-Path` header is set, it should be set as the
// the SMTP envelope sender (via the Sender field of the email struct).
if sender := em.Headers.Get(hdrReturnPath); sender != "" {
em.Sender = sender
em.Headers.Del(hdrReturnPath)
}
// If the `Bcc` header is set, it should be set on the Envelope
if bcc := em.Headers.Get(hdrBcc); bcc != "" {
for _, part := range strings.Split(bcc, ",") {
em.Bcc = append(em.Bcc, strings.TrimSpace(part))
}
em.Headers.Del(hdrBcc)
}
// If the `Cc` header is set, it should be set on the Envelope
if cc := em.Headers.Get(hdrCc); cc != "" {
for _, part := range strings.Split(cc, ",") {
em.Cc = append(em.Cc, strings.TrimSpace(part))
}
em.Headers.Del(hdrCc)
}
switch m.ContentType {
case "plain":
em.Text = []byte(m.Body)
default:
em.HTML = m.Body
if len(m.AltBody) > 0 {
em.Text = m.AltBody
}
}
return srv.pool.Send(em)
}
// Flush flushes the message queue to the server.
func (e *Emailer) Flush() error {
return nil
}
// Close closes the SMTP pools.
func (e *Emailer) Close() error {
for _, s := range e.pools[""] {
s.pool.Close()
}
return nil
}
// getPool returns the pool of servers configured to handle the given From
// header, matched by full e-mail and then by domain.
// Returns nil if no mapping matches.
func (e *Emailer) getPool(from string) []*Server {
addr := utils.ParseEmailAddress(from)
if addr == "" {
return nil
}
if srvs, ok := e.pools[addr]; ok {
return srvs
}
if _, after, ok := strings.Cut(addr, "@"); ok {
return e.pools[after]
}
return nil
}
+212
View File
@@ -0,0 +1,212 @@
package postback
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"net/http"
"net/textproto"
"time"
"source.offmarket.win/aleagle/eaglecast/models"
)
// postback is the payload that's posted as JSON to the HTTP Postback server.
//
//easyjson:json
type postback struct {
Subject string `json:"subject"`
FromEmail string `json:"from_email"`
ContentType string `json:"content_type"`
Body string `json:"body"`
Recipients []recipient `json:"recipients"`
Campaign *campaign `json:"campaign"`
Attachments []attachment `json:"attachments"`
}
type campaign struct {
FromEmail string `json:"from_email"`
UUID string `json:"uuid"`
Name string `json:"name"`
Headers models.Headers `json:"headers"`
Tags []string `json:"tags"`
}
type recipient struct {
UUID string `json:"uuid"`
Email string `json:"email"`
Name string `json:"name"`
Attribs models.JSON `json:"attribs"`
Status string `json:"status"`
}
type attachment struct {
Name string `json:"name"`
Header textproto.MIMEHeader `json:"header"`
Content []byte `json:"content"`
}
// Options represents HTTP Postback server options.
type Options struct {
Name string `json:"name"`
Username string `json:"username"`
Password string `json:"password"`
RootURL string `json:"root_url"`
MaxConns int `json:"max_conns"`
Retries int `json:"retries"`
Timeout time.Duration `json:"timeout"`
}
// Postback represents an HTTP Message server.
type Postback struct {
authStr string
o Options
c *http.Client
}
// New returns a new instance of the HTTP Postback messenger.
func New(o Options) (*Postback, error) {
authStr := ""
if o.Username != "" && o.Password != "" {
authStr = fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString(
[]byte(o.Username+":"+o.Password)))
}
return &Postback{
authStr: authStr,
o: o,
c: &http.Client{
Timeout: o.Timeout,
Transport: &http.Transport{
MaxIdleConnsPerHost: o.MaxConns,
MaxConnsPerHost: o.MaxConns,
ResponseHeaderTimeout: o.Timeout,
IdleConnTimeout: o.Timeout,
},
},
}, nil
}
// Name returns the messenger's name.
func (p *Postback) Name() string {
return p.o.Name
}
// Push pushes a message to the server.
func (p *Postback) Push(m models.Message) error {
pb := postback{
Subject: m.Subject,
FromEmail: m.From,
ContentType: m.ContentType,
Body: string(m.Body),
Recipients: []recipient{{
UUID: m.Subscriber.UUID,
Email: m.Subscriber.Email,
Name: m.Subscriber.Name,
Status: m.Subscriber.Status,
Attribs: m.Subscriber.Attribs,
}},
}
if m.Campaign != nil {
pb.Campaign = &campaign{
FromEmail: m.Campaign.FromEmail,
UUID: m.Campaign.UUID,
Name: m.Campaign.Name,
Headers: m.Campaign.Headers,
Tags: m.Campaign.Tags,
}
}
if len(m.Attachments) > 0 {
files := make([]attachment, 0, len(m.Attachments))
for _, f := range m.Attachments {
a := attachment{
Name: f.Name,
Header: f.Header,
Content: make([]byte, len(f.Content)),
}
copy(a.Content, f.Content)
files = append(files, a)
}
pb.Attachments = files
}
b, err := pb.MarshalJSON()
if err != nil {
return err
}
return p.exec(http.MethodPost, p.o.RootURL, b, nil)
}
// Flush flushes the message queue to the server.
func (p *Postback) Flush() error {
return nil
}
// Close closes idle HTTP connections.
func (p *Postback) Close() error {
p.c.CloseIdleConnections()
return nil
}
func (p *Postback) exec(method, rURL string, reqBody []byte, headers http.Header) error {
var (
err error
postBody io.Reader
)
// Encode POST / PUT params.
if method == http.MethodPost || method == http.MethodPut {
postBody = bytes.NewReader(reqBody)
}
req, err := http.NewRequest(method, rURL, postBody)
if err != nil {
return err
}
if headers != nil {
req.Header = headers
} else {
req.Header = http.Header{}
}
req.Header.Set("User-Agent", "eaglecast")
// Optional BasicAuth.
if p.authStr != "" {
req.Header.Set("Authorization", p.authStr)
}
// If a content-type isn't set, set the default one.
if req.Header.Get("Content-Type") == "" {
if method == http.MethodPost || method == http.MethodPut {
req.Header.Add("Content-Type", "application/json")
}
}
// If the request method is GET or DELETE, add the params as QueryString.
if method == http.MethodGet || method == http.MethodDelete {
req.URL.RawQuery = string(reqBody)
}
// Execute the request.
r, err := p.c.Do(req)
if err != nil {
return err
}
defer func() {
// Drain and close the body to let the Transport reuse the connection
io.Copy(io.Discard, r.Body)
r.Body.Close()
}()
if r.StatusCode != http.StatusOK {
return fmt.Errorf("non-OK response from Postback server: %d", r.StatusCode)
}
return nil
}
@@ -0,0 +1,604 @@
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package postback
import (
json "encoding/json"
models "source.offmarket.win/aleagle/eaglecast/models"
easyjson "github.com/zerodha/easyjson"
jlexer "github.com/zerodha/easyjson/jlexer"
jwriter "github.com/zerodha/easyjson/jwriter"
textproto "net/textproto"
)
// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)
func easyjsonDf11841fDecodeGithubComKnadhEagleCastInternalMessengerPostback(in *jlexer.Lexer, out *postback) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "subject":
out.Subject = string(in.String())
case "from_email":
out.FromEmail = string(in.String())
case "content_type":
out.ContentType = string(in.String())
case "body":
out.Body = string(in.String())
case "recipients":
if in.IsNull() {
in.Skip()
out.Recipients = nil
} else {
in.Delim('[')
if out.Recipients == nil {
if !in.IsDelim(']') {
out.Recipients = make([]recipient, 0, 0)
} else {
out.Recipients = []recipient{}
}
} else {
out.Recipients = (out.Recipients)[:0]
}
for !in.IsDelim(']') {
var v1 recipient
easyjsonDf11841fDecodeGithubComKnadhEagleCastInternalMessengerPostback1(in, &v1)
out.Recipients = append(out.Recipients, v1)
in.WantComma()
}
in.Delim(']')
}
case "campaign":
if in.IsNull() {
in.Skip()
out.Campaign = nil
} else {
if out.Campaign == nil {
out.Campaign = new(campaign)
}
easyjsonDf11841fDecodeGithubComKnadhEagleCastInternalMessengerPostback2(in, out.Campaign)
}
case "attachments":
if in.IsNull() {
in.Skip()
out.Attachments = nil
} else {
in.Delim('[')
if out.Attachments == nil {
if !in.IsDelim(']') {
out.Attachments = make([]attachment, 0, 1)
} else {
out.Attachments = []attachment{}
}
} else {
out.Attachments = (out.Attachments)[:0]
}
for !in.IsDelim(']') {
var v2 attachment
easyjsonDf11841fDecodeGithubComKnadhEagleCastInternalMessengerPostback3(in, &v2)
out.Attachments = append(out.Attachments, v2)
in.WantComma()
}
in.Delim(']')
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjsonDf11841fEncodeGithubComKnadhEagleCastInternalMessengerPostback(out *jwriter.Writer, in postback) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"subject\":"
out.RawString(prefix[1:])
out.String(string(in.Subject))
}
{
const prefix string = ",\"from_email\":"
out.RawString(prefix)
out.String(string(in.FromEmail))
}
{
const prefix string = ",\"content_type\":"
out.RawString(prefix)
out.String(string(in.ContentType))
}
{
const prefix string = ",\"body\":"
out.RawString(prefix)
out.String(string(in.Body))
}
{
const prefix string = ",\"recipients\":"
out.RawString(prefix)
if in.Recipients == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v3, v4 := range in.Recipients {
if v3 > 0 {
out.RawByte(',')
}
easyjsonDf11841fEncodeGithubComKnadhEagleCastInternalMessengerPostback1(out, v4)
}
out.RawByte(']')
}
}
{
const prefix string = ",\"campaign\":"
out.RawString(prefix)
if in.Campaign == nil {
out.RawString("null")
} else {
easyjsonDf11841fEncodeGithubComKnadhEagleCastInternalMessengerPostback2(out, *in.Campaign)
}
}
{
const prefix string = ",\"attachments\":"
out.RawString(prefix)
if in.Attachments == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v5, v6 := range in.Attachments {
if v5 > 0 {
out.RawByte(',')
}
easyjsonDf11841fEncodeGithubComKnadhEagleCastInternalMessengerPostback3(out, v6)
}
out.RawByte(']')
}
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v postback) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{FloatFmt: ""}
easyjsonDf11841fEncodeGithubComKnadhEagleCastInternalMessengerPostback(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v postback) MarshalEasyJSON(w *jwriter.Writer) {
easyjsonDf11841fEncodeGithubComKnadhEagleCastInternalMessengerPostback(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *postback) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjsonDf11841fDecodeGithubComKnadhEagleCastInternalMessengerPostback(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *postback) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjsonDf11841fDecodeGithubComKnadhEagleCastInternalMessengerPostback(l, v)
}
func easyjsonDf11841fDecodeGithubComKnadhEagleCastInternalMessengerPostback3(in *jlexer.Lexer, out *attachment) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "name":
out.Name = string(in.String())
case "header":
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
out.Header = make(textproto.MIMEHeader)
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v7 []string
if in.IsNull() {
in.Skip()
v7 = nil
} else {
in.Delim('[')
if v7 == nil {
if !in.IsDelim(']') {
v7 = make([]string, 0, 4)
} else {
v7 = []string{}
}
} else {
v7 = (v7)[:0]
}
for !in.IsDelim(']') {
var v8 string
v8 = string(in.String())
v7 = append(v7, v8)
in.WantComma()
}
in.Delim(']')
}
(out.Header)[key] = v7
in.WantComma()
}
in.Delim('}')
}
case "content":
if in.IsNull() {
in.Skip()
out.Content = nil
} else {
out.Content = in.Bytes()
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjsonDf11841fEncodeGithubComKnadhEagleCastInternalMessengerPostback3(out *jwriter.Writer, in attachment) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"name\":"
out.RawString(prefix[1:])
out.String(string(in.Name))
}
{
const prefix string = ",\"header\":"
out.RawString(prefix)
if in.Header == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v10First := true
for v10Name, v10Value := range in.Header {
if v10First {
v10First = false
} else {
out.RawByte(',')
}
out.String(string(v10Name))
out.RawByte(':')
if v10Value == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v11, v12 := range v10Value {
if v11 > 0 {
out.RawByte(',')
}
out.String(string(v12))
}
out.RawByte(']')
}
}
out.RawByte('}')
}
}
{
const prefix string = ",\"content\":"
out.RawString(prefix)
out.Base64Bytes(in.Content)
}
out.RawByte('}')
}
func easyjsonDf11841fDecodeGithubComKnadhEagleCastInternalMessengerPostback2(in *jlexer.Lexer, out *campaign) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "from_email":
out.FromEmail = string(in.String())
case "uuid":
out.UUID = string(in.String())
case "name":
out.Name = string(in.String())
case "headers":
if in.IsNull() {
in.Skip()
out.Headers = nil
} else {
in.Delim('[')
if out.Headers == nil {
if !in.IsDelim(']') {
out.Headers = make(models.Headers, 0, 8)
} else {
out.Headers = models.Headers{}
}
} else {
out.Headers = (out.Headers)[:0]
}
for !in.IsDelim(']') {
var v15 map[string]string
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
v15 = make(map[string]string)
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v16 string
v16 = string(in.String())
(v15)[key] = v16
in.WantComma()
}
in.Delim('}')
}
out.Headers = append(out.Headers, v15)
in.WantComma()
}
in.Delim(']')
}
case "tags":
if in.IsNull() {
in.Skip()
out.Tags = nil
} else {
in.Delim('[')
if out.Tags == nil {
if !in.IsDelim(']') {
out.Tags = make([]string, 0, 4)
} else {
out.Tags = []string{}
}
} else {
out.Tags = (out.Tags)[:0]
}
for !in.IsDelim(']') {
var v17 string
v17 = string(in.String())
out.Tags = append(out.Tags, v17)
in.WantComma()
}
in.Delim(']')
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjsonDf11841fEncodeGithubComKnadhEagleCastInternalMessengerPostback2(out *jwriter.Writer, in campaign) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"from_email\":"
out.RawString(prefix[1:])
out.String(string(in.FromEmail))
}
{
const prefix string = ",\"uuid\":"
out.RawString(prefix)
out.String(string(in.UUID))
}
{
const prefix string = ",\"name\":"
out.RawString(prefix)
out.String(string(in.Name))
}
{
const prefix string = ",\"headers\":"
out.RawString(prefix)
if in.Headers == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v18, v19 := range in.Headers {
if v18 > 0 {
out.RawByte(',')
}
if v19 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v20First := true
for v20Name, v20Value := range v19 {
if v20First {
v20First = false
} else {
out.RawByte(',')
}
out.String(string(v20Name))
out.RawByte(':')
out.String(string(v20Value))
}
out.RawByte('}')
}
}
out.RawByte(']')
}
}
{
const prefix string = ",\"tags\":"
out.RawString(prefix)
if in.Tags == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v21, v22 := range in.Tags {
if v21 > 0 {
out.RawByte(',')
}
out.String(string(v22))
}
out.RawByte(']')
}
}
out.RawByte('}')
}
func easyjsonDf11841fDecodeGithubComKnadhEagleCastInternalMessengerPostback1(in *jlexer.Lexer, out *recipient) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "uuid":
out.UUID = string(in.String())
case "email":
out.Email = string(in.String())
case "name":
out.Name = string(in.String())
case "attribs":
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
out.Attribs = make(models.JSON)
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v23 interface{}
if m, ok := v23.(easyjson.Unmarshaler); ok {
m.UnmarshalEasyJSON(in)
} else if m, ok := v23.(json.Unmarshaler); ok {
_ = m.UnmarshalJSON(in.Raw())
} else {
v23 = in.Interface()
}
(out.Attribs)[key] = v23
in.WantComma()
}
in.Delim('}')
}
case "status":
out.Status = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjsonDf11841fEncodeGithubComKnadhEagleCastInternalMessengerPostback1(out *jwriter.Writer, in recipient) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"uuid\":"
out.RawString(prefix[1:])
out.String(string(in.UUID))
}
{
const prefix string = ",\"email\":"
out.RawString(prefix)
out.String(string(in.Email))
}
{
const prefix string = ",\"name\":"
out.RawString(prefix)
out.String(string(in.Name))
}
{
const prefix string = ",\"attribs\":"
out.RawString(prefix)
if in.Attribs == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v24First := true
for v24Name, v24Value := range in.Attribs {
if v24First {
v24First = false
} else {
out.RawByte(',')
}
out.String(string(v24Name))
out.RawByte(':')
if m, ok := v24Value.(easyjson.Marshaler); ok {
m.MarshalEasyJSON(out)
} else if m, ok := v24Value.(json.Marshaler); ok {
out.Raw(m.MarshalJSON())
} else {
out.Raw(json.Marshal(v24Value))
}
}
out.RawByte('}')
}
}
{
const prefix string = ",\"status\":"
out.RawString(prefix)
out.String(string(in.Status))
}
out.RawByte('}')
}