@@ -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
|
||||
}
|
||||
@@ -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('}')
|
||||
}
|
||||
Reference in New Issue
Block a user