Add progress indicator/animation

master v1.2.0
Abner Coimbre 2025-10-22 00:58:20 -07:00
parent b35ac045f3
commit 320bb5c34a
1 changed files with 75 additions and 7 deletions

82
main.go
View File

@ -1,6 +1,6 @@
// Meetup Invite 2000 // Meetup Invite 2000
// @author: Abner Coimbre <abner@handmadecities.com> // @author: Abner Coimbre <abner@handmadecities.com>
// Copyright (c) 2024 Handmade Cities // Copyright (c) Handmade Cities LLC
// Based off News Blaster 2000 from the Handmade Network: https://git.handmade.network/hmn/newsblaster2000 // Based off News Blaster 2000 from the Handmade Network: https://git.handmade.network/hmn/newsblaster2000
@ -12,7 +12,7 @@
// Jack Punter (@TarriestPython) // Jack Punter (@TarriestPython)
// //
// Emotional Support // Emotional Support
// Cucui Ganon Rosario // Cucui Ganon Rosario: https://www.youtube.com/watch?v=bvCsTca0uBc
// ========================================================================= // =========================================================================
package main package main
@ -306,14 +306,18 @@ func blastMail(cfg *Config, logFile string, trackingFile string, audience []stri
if len(group) == cfg.BatchSize { if len(group) == cfg.BatchSize {
results, err := sendMail(cfg, group, subject, body) results, err := sendMail(cfg, group, subject, body)
if err != nil { if err != nil {
fmt.Printf("Error while sending mail: %v\n", err)
return return
} }
sentToAddresses = append(sentToAddresses, group...) sentToAddresses = append(sentToAddresses, group...)
os.WriteFile(trackingFile, []byte(strings.Join(sentToAddresses, "\n")), 0666) os.WriteFile(trackingFile, []byte(strings.Join(sentToAddresses, "\n")), 0666)
var failed []string
for i, res := range results { for i, res := range results {
log.WriteString(fmt.Sprintf("%s: %s\n", group[i], res.Message)) log.WriteString(fmt.Sprintf("%s: %s\n", group[i], res.Message))
if res.ErrorCode != 0 {
failed = append(failed, group[i])
} }
}
printBatchSummary(group, failed)
newReaderCount += len(group) newReaderCount += len(group)
group = group[0:0] group = group[0:0]
} }
@ -322,14 +326,18 @@ func blastMail(cfg *Config, logFile string, trackingFile string, audience []stri
if len(group) > 0 { if len(group) > 0 {
results, err := sendMail(cfg, group, subject, body) results, err := sendMail(cfg, group, subject, body)
if err != nil { if err != nil {
fmt.Printf("Error while sending mail: %v\n", err)
return return
} }
sentToAddresses = append(sentToAddresses, group...) sentToAddresses = append(sentToAddresses, group...)
os.WriteFile(trackingFile, []byte(strings.Join(sentToAddresses, "\n")), 0666) os.WriteFile(trackingFile, []byte(strings.Join(sentToAddresses, "\n")), 0666)
var failed []string
for i, res := range results { for i, res := range results {
log.WriteString(fmt.Sprintf("%s: %s\n", group[i], res.Message)) log.WriteString(fmt.Sprintf("%s: %s\n", group[i], res.Message))
if res.ErrorCode != 0 {
failed = append(failed, group[i])
} }
}
printBatchSummary(group, failed)
newReaderCount += len(group) newReaderCount += len(group)
} }
@ -369,8 +377,47 @@ type PostmarkBatchResult struct {
Message string `json:"Message"` Message string `json:"Message"`
} }
func printBatchSummary(sent, failed []string) {
if len(failed) == 0 {
fmt.Printf("\033[32m✅ Sent %d messages\033[0m\n", len(sent))
} else {
successCount := len(sent) - len(failed)
if successCount < 0 {
successCount = 0
}
fmt.Printf("\033[31m❌ Sent %d, failed %d\033[0m\n", successCount, len(failed))
for _, f := range failed {
fmt.Printf(" - %s\n", f)
}
}
}
func sendMail(cfg *Config, recipients []string, subject, html string) ([]PostmarkBatchResult, error) { func sendMail(cfg *Config, recipients []string, subject, html string) ([]PostmarkBatchResult, error) {
fmt.Printf("Sending batch [%d recipients]...", len(recipients)) /* Start a tiny spinner while the HTTP call runs.
* Uses \r to update the same line. Stops as soon as the network call returns.
*/
stop := make(chan struct{})
done := make(chan struct{})
go func() {
frames := []rune{'|', '/', '-', '\\'}
i := 0
for {
select {
case <-stop:
close(done)
return
default:
fmt.Printf("\rSending batch [%d recipients]... %c", len(recipients), frames[i%len(frames)])
i++
time.Sleep(120 * time.Millisecond)
}
}
}()
/*
* Prepare and make the network call
*/
from := cfg.Postmark.SenderEmail from := cfg.Postmark.SenderEmail
if cfg.Postmark.SenderName != "" { if cfg.Postmark.SenderName != "" {
@ -400,13 +447,21 @@ func sendMail(cfg *Config, recipients []string, subject, html string) ([]Postmar
req, err := http.NewRequest(http.MethodPost, "https://api.postmarkapp.com/email/batchWithTemplates", bytes.NewReader(reqBody)) req, err := http.NewRequest(http.MethodPost, "https://api.postmarkapp.com/email/batchWithTemplates", bytes.NewReader(reqBody))
if err != nil { if err != nil {
// stop spinner before returning
close(stop)
<-done
fmt.Printf("\r")
return nil, err return nil, err
} }
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Postmark-Server-Token", cfg.Postmark.ServerToken) req.Header.Set("X-Postmark-Server-Token", cfg.Postmark.ServerToken)
res, err := postmarkClient.Do(req) res, err := postmarkClient.Do(req)
if err != nil { if err != nil {
close(stop)
<-done
fmt.Printf("\r\033[31m❌ Sending batch [%d recipients]... error: %v\033[0m\n", len(recipients), err)
return nil, err return nil, err
} }
@ -414,17 +469,30 @@ func sendMail(cfg *Config, recipients []string, subject, html string) ([]Postmar
resBody, err := io.ReadAll(res.Body) resBody, err := io.ReadAll(res.Body)
res.Body.Close() res.Body.Close()
if err != nil { if err != nil {
close(stop)
<-done
fmt.Printf("\r")
return nil, err return nil, err
} }
if res.StatusCode != 200 { if res.StatusCode != 200 {
close(stop)
<-done
fmt.Printf("\r\033[31m❌ Bad response from postmark: %d\033[0m\n", res.StatusCode)
return nil, fmt.Errorf("Bad response from postmark: %d", res.StatusCode) return nil, fmt.Errorf("Bad response from postmark: %d", res.StatusCode)
} }
err = json.Unmarshal(resBody, &results) err = json.Unmarshal(resBody, &results)
if err != nil { if err != nil {
fmt.Printf("Batch sent successfully, but failed to parse response from postmark.\n") close(stop)
<-done
fmt.Printf("\r\033[33m⚠ Batch sent but failed to parse Postmark response\033[0m\n")
return nil, nil return nil, nil
} }
fmt.Printf("Done.\n")
close(stop)
<-done
fmt.Printf("\r\033[32m✅ Emailed batch of %d recipients\033[0m\n", len(recipients))
return results, nil return results, nil
} }