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"
|
||||||
"github.com/therootcompany/golib/net/smsgw/androidsmsgateway"
|
"github.com/therootcompany/golib/net/smsgw/androidsmsgateway"
|
||||||
"github.com/therootcompany/golib/net/smsgw/smscsv"
|
"github.com/therootcompany/golib/net/smsgw/smscsv"
|
||||||
"github.com/therootcompany/golib/net/smsgw/smstmpl"
|
"github.com/therootcompany/golib/text/textvars"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MainConfig struct {
|
type MainConfig struct {
|
||||||
@ -124,7 +124,7 @@ func main() {
|
|||||||
csvr := csv.NewReader(file)
|
csvr := csv.NewReader(file)
|
||||||
csvr.FieldsPerRecord = -1
|
csvr.FieldsPerRecord = -1
|
||||||
|
|
||||||
messages, warns, err := smscsv.ReadOrIgnoreAll(csvr)
|
messages, warns, err := smscsv.ReadOrIgnoreAll(csvr, "Name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "\n%sError%s: %v\n", textErr, textReset, err)
|
fmt.Fprintf(os.Stderr, "\n%sError%s: %v\n", textErr, textReset, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -144,12 +144,21 @@ func main() {
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Info: list of %d messages\n", len(messages))
|
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 {
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message.Text = text
|
||||||
|
messages[i] = message
|
||||||
|
}
|
||||||
|
|
||||||
if now.After(cfg.endTime) || now.Equal(cfg.endTime) {
|
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)
|
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, "\n")
|
||||||
fmt.Fprintf(os.Stderr, "Info: This is what a %ssample message%s from list look like:\n", textInfo, textReset)
|
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, "\n")
|
||||||
fmt.Fprintf(os.Stderr, " To: %s (%s)\n", messages[0].Number, messages[0].Name())
|
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", textTmpl, messages[0].Template, textReset)
|
||||||
fmt.Fprintf(os.Stderr, " %s%s%s\n", textInfo, messages[0].Text(), textReset)
|
fmt.Fprintf(os.Stderr, " %s%s%s\n", textInfo, messages[0].Text, textReset)
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
if !cfg.confirmed && !cfg.dryRun {
|
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:])
|
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 {
|
if cfg.printCurl {
|
||||||
curl := sender.CurlString(message.Number, message.Text())
|
curl := sender.CurlString(message.Number, message.Text)
|
||||||
fmt.Println(curl)
|
fmt.Println(curl)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", message.Text())
|
fmt.Fprintf(os.Stderr, "%s\n", message.Text)
|
||||||
}
|
}
|
||||||
if cfg.dryRun {
|
if cfg.dryRun {
|
||||||
continue
|
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)
|
fmt.Fprintf(os.Stderr, "%sError%s: %v\n", textErr, textReset, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package smscsv
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -26,121 +27,90 @@ func (w CSVWarn) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
Record `csv:"*"`
|
||||||
|
Number string `csv:"Phone"`
|
||||||
|
Template string `csv:"Message"`
|
||||||
|
Text string `csv:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Record struct {
|
||||||
header []string
|
header []string
|
||||||
indices map[string]int
|
|
||||||
fields []string
|
fields []string
|
||||||
name string
|
|
||||||
Number string
|
|
||||||
template string
|
|
||||||
Vars map[string]string
|
|
||||||
text string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) Name() string {
|
func (r Record) Keys() []string {
|
||||||
return m.name
|
return r.header
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) Template() string {
|
func (r Record) Get(key string) string {
|
||||||
return m.template
|
// typically there are only a few fields, so indexing is faster than mapping
|
||||||
}
|
i := slices.Index(r.header, key)
|
||||||
|
if i < 0 {
|
||||||
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 {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.fields) >= 1+index {
|
return r.fields[i]
|
||||||
return m.fields[index]
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// TODO XXX AJ pass in column name mapping
|
||||||
func ReadOrIgnoreAll(csvr Reader) (messages []Message, warns []CSVWarn, err error) {
|
func ReadOrIgnoreAll(csvr Reader, labelKey string) (messages []Message, warns []CSVWarn, err error) {
|
||||||
header, err := csvr.Read()
|
dec, err := csvutil.NewDecoder(csvr)
|
||||||
if err != nil {
|
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")
|
header := dec.Header()
|
||||||
FIELD_PHONE := GetFieldIndex(header, "Phone")
|
if GetFieldIndex(header, "Phone") == -1 || GetFieldIndex(header, "Message") == -1 {
|
||||||
FIELD_MESSAGE := GetFieldIndex(header, "Message")
|
|
||||||
if FIELD_NAME == -1 || FIELD_PHONE == -1 || FIELD_MESSAGE == -1 {
|
|
||||||
return nil, nil, fmt.Errorf("header is missing one or more of 'Name', 'Phone', and/or 'Message'")
|
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
|
rowIndex := 1 // 1-index, start at header
|
||||||
for {
|
for {
|
||||||
rowIndex++
|
rowIndex++
|
||||||
rec, err := csvr.Read()
|
|
||||||
if err == io.EOF {
|
m := Record(header)
|
||||||
|
if err := dec.Decode(&m); err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
} else if err != nil {
|
||||||
if err != nil {
|
log.Fatal(err)
|
||||||
return nil, nil, fmt.Errorf("failed to parse row %d (and all following rows): %w", rowIndex, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO XXX AJ create an abstraction around the header []string and the record []string
|
// TODO we can't use this optimization when the fields have different lengths
|
||||||
// the idea is to return the same thing for valid and invalid rows
|
if unusedHeader == nil {
|
||||||
vars := make(map[string]string)
|
ids := dec.Unused()
|
||||||
n := min(len(header), len(rec))
|
unusedHeader = make([]string, len(ids))
|
||||||
for i := range n {
|
|
||||||
switch i {
|
|
||||||
case FIELD_NAME, FIELD_PHONE, FIELD_MESSAGE:
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
key := header[i]
|
|
||||||
val := rec[i]
|
|
||||||
vars[key] = val
|
|
||||||
}
|
}
|
||||||
|
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 {
|
m.Number = smsgw.StripFormatting(m.Number)
|
||||||
warns = append(warns, CSVWarn{
|
m.Number, err = smsgw.PrefixUS10Digit(m.Number)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warns = append(warns, CSVWarn{
|
warns = append(warns, CSVWarn{
|
||||||
Index: rowIndex,
|
Index: rowIndex,
|
||||||
Code: "PhoneInvalid",
|
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,
|
// Record: rec,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
messages = append(messages, m)
|
||||||
messages = append(messages, message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages, warns, nil
|
return messages, warns, nil
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user