mirror of
https://github.com/therootcompany/golib.git
synced 2026-01-27 23:18:05 +00:00
wip: sendsms use csvutil
This commit is contained in:
parent
65b6438f6d
commit
71e41ecdad
@ -15,7 +15,7 @@ import (
|
||||
"github.com/therootcompany/golib/net/smsgw"
|
||||
"github.com/therootcompany/golib/net/smsgw/androidsmsgateway"
|
||||
"github.com/therootcompany/golib/net/smsgw/smscsv"
|
||||
"github.com/therootcompany/golib/net/smsgw/smstmpl"
|
||||
"github.com/therootcompany/golib/text/textvars"
|
||||
)
|
||||
|
||||
type MainConfig struct {
|
||||
@ -124,7 +124,7 @@ func main() {
|
||||
csvr := csv.NewReader(file)
|
||||
csvr.FieldsPerRecord = -1
|
||||
|
||||
messages, warns, err := smscsv.ReadOrIgnoreAll(csvr)
|
||||
messages, warns, err := smscsv.ReadOrIgnoreAll(csvr, "Name")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "\n%sError%s: %v\n", textErr, textReset, err)
|
||||
os.Exit(1)
|
||||
@ -144,12 +144,21 @@ func main() {
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Info: list of %d messages\n", len(messages))
|
||||
|
||||
messages, err = smstmpl.RenderAll(messages)
|
||||
for i, message := range messages {
|
||||
rowIndex := i + 1
|
||||
text, err := textvars.ReplaceVars(message.Template, message.Map())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "\n%sError%s: %v\n", textErr, textReset, err)
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"\n%sError%s: failing due to row %d (%s): %v",
|
||||
textErr, textReset, rowIndex, message.Get("Name"), err,
|
||||
)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
message.Text = text
|
||||
messages[i] = message
|
||||
}
|
||||
|
||||
if now.After(cfg.endTime) || now.Equal(cfg.endTime) {
|
||||
fmt.Fprintf(os.Stderr, "%sWarning%s: Too late now. %sWaiting until tomorrow%s:\n", textWarn, textReset, textWarn, textReset)
|
||||
|
||||
@ -239,9 +248,9 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, "Info: This is what a %ssample message%s from list look like:\n", textInfo, textReset)
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " To: %s (%s)\n", messages[0].Number, messages[0].Name())
|
||||
fmt.Fprintf(os.Stderr, " %s%s%s\n", textTmpl, messages[0].Template(), textReset)
|
||||
fmt.Fprintf(os.Stderr, " %s%s%s\n", textInfo, messages[0].Text(), textReset)
|
||||
fmt.Fprintf(os.Stderr, " To: %s (%s)\n", messages[0].Number, messages[0].Get("Name"))
|
||||
fmt.Fprintf(os.Stderr, " %s%s%s\n", textTmpl, messages[0].Template, textReset)
|
||||
fmt.Fprintf(os.Stderr, " %s%s%s\n", textInfo, messages[0].Text, textReset)
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
if !cfg.confirmed && !cfg.dryRun {
|
||||
@ -290,16 +299,16 @@ func main() {
|
||||
|
||||
fmt.Fprintf(os.Stderr, "# Send to %s (%s) %s-%s\n", message.Number[:2], message.Number[2:5], message.Number[5:8], message.Number[8:])
|
||||
if cfg.printCurl {
|
||||
curl := sender.CurlString(message.Number, message.Text())
|
||||
curl := sender.CurlString(message.Number, message.Text)
|
||||
fmt.Println(curl)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", message.Text())
|
||||
fmt.Fprintf(os.Stderr, "%s\n", message.Text)
|
||||
}
|
||||
if cfg.dryRun {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := sender.Send(message.Number, message.Text()); err != nil {
|
||||
if err := sender.Send(message.Number, message.Text); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%sError%s: %v\n", textErr, textReset, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package smscsv
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@ -26,121 +27,90 @@ func (w CSVWarn) Error() string {
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Record `csv:"*"`
|
||||
Number string `csv:"Phone"`
|
||||
Template string `csv:"Message"`
|
||||
Text string `csv:"-"`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
header []string
|
||||
indices map[string]int
|
||||
fields []string
|
||||
name string
|
||||
Number string
|
||||
template string
|
||||
Vars map[string]string
|
||||
text string
|
||||
}
|
||||
|
||||
func (m Message) Name() string {
|
||||
return m.name
|
||||
func (r Record) Keys() []string {
|
||||
return r.header
|
||||
}
|
||||
|
||||
func (m Message) Template() string {
|
||||
return m.template
|
||||
}
|
||||
|
||||
func (m Message) Text() string {
|
||||
return m.text
|
||||
}
|
||||
|
||||
func (m *Message) SetText(text string) {
|
||||
m.text = text
|
||||
}
|
||||
|
||||
func (m Message) Size() int {
|
||||
return len(m.fields)
|
||||
}
|
||||
|
||||
func (m Message) Get(key string) string {
|
||||
index, ok := m.indices[key]
|
||||
if !ok {
|
||||
func (r Record) Get(key string) string {
|
||||
// typically there are only a few fields, so indexing is faster than mapping
|
||||
i := slices.Index(r.header, key)
|
||||
if i < 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(m.fields) >= 1+index {
|
||||
return m.fields[index]
|
||||
}
|
||||
return r.fields[i]
|
||||
}
|
||||
|
||||
return ""
|
||||
func (r Record) Map() map[string]string {
|
||||
m := make(map[string]string, len(r.header))
|
||||
for i, k := range r.header {
|
||||
m[k] = r.fields[i]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// TODO XXX AJ pass in column name mapping
|
||||
func ReadOrIgnoreAll(csvr Reader) (messages []Message, warns []CSVWarn, err error) {
|
||||
header, err := csvr.Read()
|
||||
func ReadOrIgnoreAll(csvr Reader, labelKey string) (messages []Message, warns []CSVWarn, err error) {
|
||||
dec, err := csvutil.NewDecoder(csvr)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("header could not be parsed: %w", err)
|
||||
return nil, nil, err
|
||||
// fmt.Fprintf(os.Stderr, "\n%sError%s: %v\n", textErr, textReset, err)
|
||||
// os.Exit(1)
|
||||
}
|
||||
|
||||
FIELD_NAME := GetFieldIndex(header, "Name")
|
||||
FIELD_PHONE := GetFieldIndex(header, "Phone")
|
||||
FIELD_MESSAGE := GetFieldIndex(header, "Message")
|
||||
if FIELD_NAME == -1 || FIELD_PHONE == -1 || FIELD_MESSAGE == -1 {
|
||||
header := dec.Header()
|
||||
if GetFieldIndex(header, "Phone") == -1 || GetFieldIndex(header, "Message") == -1 {
|
||||
return nil, nil, fmt.Errorf("header is missing one or more of 'Name', 'Phone', and/or 'Message'")
|
||||
}
|
||||
FIELD_MIN := 1 + slices.Max([]int{FIELD_NAME, FIELD_PHONE, FIELD_MESSAGE})
|
||||
|
||||
var unusedHeader []string
|
||||
rowIndex := 1 // 1-index, start at header
|
||||
for {
|
||||
rowIndex++
|
||||
rec, err := csvr.Read()
|
||||
if err == io.EOF {
|
||||
|
||||
m := Record(header)
|
||||
if err := dec.Decode(&m); err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse row %d (and all following rows): %w", rowIndex, err)
|
||||
} else if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// TODO XXX AJ create an abstraction around the header []string and the record []string
|
||||
// the idea is to return the same thing for valid and invalid rows
|
||||
vars := make(map[string]string)
|
||||
n := min(len(header), len(rec))
|
||||
for i := range n {
|
||||
switch i {
|
||||
case FIELD_NAME, FIELD_PHONE, FIELD_MESSAGE:
|
||||
continue
|
||||
default:
|
||||
key := header[i]
|
||||
val := rec[i]
|
||||
vars[key] = val
|
||||
// TODO we can't use this optimization when the fields have different lengths
|
||||
if unusedHeader == nil {
|
||||
ids := dec.Unused()
|
||||
unusedHeader = make([]string, len(ids))
|
||||
}
|
||||
m.Fields = Record{
|
||||
header: unusedHeader,
|
||||
fields: make([]string, len(unusedHeader)),
|
||||
}
|
||||
for _, i := range dec.Unused() {
|
||||
m.Fields.fields[i] = dec.Record()[i]
|
||||
}
|
||||
|
||||
if len(rec) < FIELD_MIN {
|
||||
warns = append(warns, CSVWarn{
|
||||
Index: rowIndex,
|
||||
Code: "TooFewFields",
|
||||
Message: fmt.Sprintf("ignoring row %d: too few fields (want %d, have %d)", rowIndex, FIELD_MIN, len(rec)),
|
||||
Record: rec,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
message := Message{
|
||||
// Index: rowIndex,
|
||||
name: strings.TrimSpace(rec[FIELD_NAME]),
|
||||
Number: strings.TrimSpace(rec[FIELD_PHONE]),
|
||||
template: strings.TrimSpace(rec[FIELD_MESSAGE]),
|
||||
Vars: vars,
|
||||
}
|
||||
|
||||
message.Number = smsgw.StripFormatting(message.Number)
|
||||
message.Number, err = smsgw.PrefixUS10Digit(message.Number)
|
||||
m.Number = smsgw.StripFormatting(m.Number)
|
||||
m.Number, err = smsgw.PrefixUS10Digit(m.Number)
|
||||
if err != nil {
|
||||
warns = append(warns, CSVWarn{
|
||||
Index: rowIndex,
|
||||
Code: "PhoneInvalid",
|
||||
Message: fmt.Sprintf("ignoring row %d (%s): %s", rowIndex, message.Name(), err.Error()),
|
||||
Message: fmt.Sprintf("ignoring row %d (%s): %s", rowIndex, m.Fields.Get(labelKey), err.Error()),
|
||||
// Record: rec,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
messages = append(messages, message)
|
||||
messages = append(messages, m)
|
||||
}
|
||||
|
||||
return messages, warns, nil
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user