main.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package main
  2. import (
  3. "crypto/md5"
  4. "database/sql"
  5. "encoding/hex"
  6. "encoding/json"
  7. "fmt"
  8. "net/http"
  9. "strings"
  10. "time"
  11. "github.com/sirupsen/logrus"
  12. "github.com/spf13/cobra"
  13. "github.com/spf13/viper"
  14. _ "github.com/go-sql-driver/mysql"
  15. )
  16. type Member struct {
  17. EmailAddress string `json:"email_address"`
  18. Status string `json:"status_if_new,omitempty"`
  19. }
  20. func initConfig() {
  21. viper.SetConfigName("config")
  22. viper.AddConfigPath(".")
  23. viper.AutomaticEnv()
  24. if err := viper.ReadInConfig(); err != nil {
  25. logrus.Fatalf("Error reading config file: %v", err)
  26. }
  27. }
  28. func getEmailsFromMySQL() ([]string, error) {
  29. db, err := sql.Open("mysql", viper.GetString("mysql.dsn"))
  30. if err != nil {
  31. return nil, err
  32. }
  33. defer db.Close()
  34. rows, err := db.Query(viper.GetString("mysql.query"))
  35. if err != nil {
  36. return nil, err
  37. }
  38. defer rows.Close()
  39. var emails []string
  40. for rows.Next() {
  41. var email string
  42. if err := rows.Scan(&email); err == nil {
  43. emails = append(emails, strings.ToLower(strings.TrimSpace(email)))
  44. }
  45. }
  46. return emails, nil
  47. }
  48. func emailHash(email string) string {
  49. hash := md5.Sum([]byte(strings.ToLower(email)))
  50. return hex.EncodeToString(hash[:])
  51. }
  52. func emailExists(email string) (bool, error) {
  53. hashStr := emailHash(email)
  54. url := fmt.Sprintf("https://%s.api.mailchimp.com/3.0/lists/%s/members/%s",
  55. viper.GetString("mailchimp.server"),
  56. viper.GetString("mailchimp.audience_id"),
  57. hashStr)
  58. req, _ := http.NewRequest("GET", url, nil)
  59. req.SetBasicAuth("anystring", viper.GetString("mailchimp.api_key"))
  60. resp, err := http.DefaultClient.Do(req)
  61. if err != nil {
  62. return false, err
  63. }
  64. defer resp.Body.Close()
  65. return resp.StatusCode == http.StatusOK, nil
  66. }
  67. func addEmail(email string) error {
  68. url := fmt.Sprintf("https://%s.api.mailchimp.com/3.0/lists/%s/members",
  69. viper.GetString("mailchimp.server"),
  70. viper.GetString("mailchimp.audience_id"))
  71. member := Member{
  72. EmailAddress: email,
  73. Status: "subscribed",
  74. }
  75. body, _ := json.Marshal(member)
  76. req, _ := http.NewRequest("POST", url, strings.NewReader(string(body)))
  77. req.SetBasicAuth("anystring", viper.GetString("mailchimp.api_key"))
  78. req.Header.Add("Content-Type", "application/json")
  79. resp, err := http.DefaultClient.Do(req)
  80. if err != nil {
  81. return err
  82. }
  83. defer resp.Body.Close()
  84. if resp.StatusCode >= 400 {
  85. return fmt.Errorf("failed to add email: %s", email)
  86. }
  87. return nil
  88. }
  89. func tagEmail(email, tag string) error {
  90. hashStr := emailHash(email)
  91. url := fmt.Sprintf("https://%s.api.mailchimp.com/3.0/lists/%s/members/%s/tags",
  92. viper.GetString("mailchimp.server"),
  93. viper.GetString("mailchimp.audience_id"),
  94. hashStr)
  95. body := map[string]interface{}{
  96. "tags": []map[string]string{
  97. {"name": tag, "status": "active"},
  98. },
  99. }
  100. jsonBody, _ := json.Marshal(body)
  101. req, _ := http.NewRequest("POST", url, strings.NewReader(string(jsonBody)))
  102. req.SetBasicAuth("anystring", viper.GetString("mailchimp.api_key"))
  103. req.Header.Add("Content-Type", "application/json")
  104. resp, err := http.DefaultClient.Do(req)
  105. if err != nil {
  106. return err
  107. }
  108. defer resp.Body.Close()
  109. if resp.StatusCode >= 400 {
  110. return fmt.Errorf("failed to tag email: %s", email)
  111. }
  112. return nil
  113. }
  114. var rootCmd = &cobra.Command{
  115. Use: "sync-mailchimp",
  116. Short: "Sync MySQL emails with Mailchimp",
  117. Run: func(cmd *cobra.Command, args []string) {
  118. emails, err := getEmailsFromMySQL()
  119. if err != nil {
  120. logrus.Fatalf("MySQL error: %v", err)
  121. }
  122. var toAdd []string
  123. for _, email := range emails {
  124. exists, err := emailExists(email)
  125. if err != nil {
  126. logrus.Warnf("Check failed for %s: %v", email, err)
  127. continue
  128. }
  129. if !exists {
  130. toAdd = append(toAdd, email)
  131. }
  132. }
  133. if len(toAdd) > 0 {
  134. fmt.Printf("%d emails not found. Sync? [y/N]: ", len(toAdd))
  135. var input string
  136. fmt.Scanln(&input)
  137. if strings.ToLower(input) == "y" {
  138. for _, email := range toAdd {
  139. if err := addEmail(email); err != nil {
  140. logrus.Errorf("Failed to add %s: %v", email, err)
  141. } else {
  142. logrus.Infof("Added %s", email)
  143. }
  144. }
  145. }
  146. }
  147. tag := time.Now().Format("January 2006")
  148. for _, email := range emails {
  149. if err := tagEmail(email, tag); err != nil {
  150. logrus.Warnf("Failed to tag %s: %v", email, err)
  151. } else {
  152. logrus.Infof("Tagged %s with %s", email, tag)
  153. }
  154. }
  155. },
  156. }
  157. func main() {
  158. cobra.OnInitialize(initConfig)
  159. if err := rootCmd.Execute(); err != nil {
  160. logrus.Fatalf("Command failed: %v", err)
  161. }
  162. }