package main import ( "crypto/md5" "database/sql" "encoding/hex" "encoding/json" "fmt" "net/http" "strings" "time" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" _ "github.com/go-sql-driver/mysql" ) type Member struct { EmailAddress string `json:"email_address"` Status string `json:"status_if_new,omitempty"` } func initConfig() { viper.SetConfigName("config") viper.AddConfigPath(".") viper.AutomaticEnv() if err := viper.ReadInConfig(); err != nil { logrus.Fatalf("Error reading config file: %v", err) } } func getEmailsFromMySQL() ([]string, error) { db, err := sql.Open("mysql", viper.GetString("mysql.dsn")) if err != nil { return nil, err } defer db.Close() rows, err := db.Query(viper.GetString("mysql.query")) if err != nil { return nil, err } defer rows.Close() var emails []string for rows.Next() { var email string if err := rows.Scan(&email); err == nil { emails = append(emails, strings.ToLower(strings.TrimSpace(email))) } } return emails, nil } func emailHash(email string) string { hash := md5.Sum([]byte(strings.ToLower(email))) return hex.EncodeToString(hash[:]) } func emailExists(email string) (bool, error) { hashStr := emailHash(email) url := fmt.Sprintf("https://%s.api.mailchimp.com/3.0/lists/%s/members/%s", viper.GetString("mailchimp.server"), viper.GetString("mailchimp.audience_id"), hashStr) req, _ := http.NewRequest("GET", url, nil) req.SetBasicAuth("anystring", viper.GetString("mailchimp.api_key")) resp, err := http.DefaultClient.Do(req) if err != nil { return false, err } defer resp.Body.Close() return resp.StatusCode == http.StatusOK, nil } func addEmail(email string) error { url := fmt.Sprintf("https://%s.api.mailchimp.com/3.0/lists/%s/members", viper.GetString("mailchimp.server"), viper.GetString("mailchimp.audience_id")) member := Member{ EmailAddress: email, Status: "subscribed", } body, _ := json.Marshal(member) req, _ := http.NewRequest("POST", url, strings.NewReader(string(body))) req.SetBasicAuth("anystring", viper.GetString("mailchimp.api_key")) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 400 { return fmt.Errorf("failed to add email: %s", email) } return nil } func tagEmail(email, tag string) error { hashStr := emailHash(email) url := fmt.Sprintf("https://%s.api.mailchimp.com/3.0/lists/%s/members/%s/tags", viper.GetString("mailchimp.server"), viper.GetString("mailchimp.audience_id"), hashStr) body := map[string]interface{}{ "tags": []map[string]string{ {"name": tag, "status": "active"}, }, } jsonBody, _ := json.Marshal(body) req, _ := http.NewRequest("POST", url, strings.NewReader(string(jsonBody))) req.SetBasicAuth("anystring", viper.GetString("mailchimp.api_key")) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 400 { return fmt.Errorf("failed to tag email: %s", email) } return nil } var rootCmd = &cobra.Command{ Use: "sync-mailchimp", Short: "Sync MySQL emails with Mailchimp", Run: func(cmd *cobra.Command, args []string) { emails, err := getEmailsFromMySQL() if err != nil { logrus.Fatalf("MySQL error: %v", err) } var toAdd []string for _, email := range emails { exists, err := emailExists(email) if err != nil { logrus.Warnf("Check failed for %s: %v", email, err) continue } if !exists { toAdd = append(toAdd, email) } } if len(toAdd) > 0 { fmt.Printf("%d emails not found. Sync? [y/N]: ", len(toAdd)) var input string fmt.Scanln(&input) if strings.ToLower(input) == "y" { for _, email := range toAdd { if err := addEmail(email); err != nil { logrus.Errorf("Failed to add %s: %v", email, err) } else { logrus.Infof("Added %s", email) } } } } tag := time.Now().Format("January 2006") for _, email := range emails { if err := tagEmail(email, tag); err != nil { logrus.Warnf("Failed to tag %s: %v", email, err) } else { logrus.Infof("Tagged %s with %s", email, tag) } } }, } func main() { cobra.OnInitialize(initConfig) if err := rootCmd.Execute(); err != nil { logrus.Fatalf("Command failed: %v", err) } }