diff --git a/sendcloud.go b/sendcloud.go index 01b8454..309a070 100644 --- a/sendcloud.go +++ b/sendcloud.go @@ -1,13 +1,12 @@ package go_sms_sender import ( - "crypto/md5" + "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "io" - "log" "net/http" "net/url" "strconv" @@ -15,6 +14,7 @@ import ( "time" ) +// ResponseData structure holds the response from SendCloud API. type ResponseData struct { Result bool `json:"result"` StatusCode int `json:"statusCode"` @@ -22,6 +22,7 @@ type ResponseData struct { Info string `json:"info"` } +// Config holds the configuration for the SMS sending service. type Config struct { CharSet string Server string @@ -31,24 +32,26 @@ type Config struct { MaxReceivers int } -func NewConfig(smsUser, smsKey string) (Config, error) { +// NewConfig creates a new configuration with the provided user and key. +func NewConfig(smsUser, smsKey string) (*Config, error) { if smsUser == "" { - return Config{}, errors.New("smsUser cannot be empty") + return nil, errors.New("smsUser cannot be empty") } if smsKey == "" { - return Config{}, errors.New("smsKey cannot be empty") + return nil, errors.New("smsKey cannot be empty") } - return Config{ + return &Config{ CharSet: "utf-8", Server: "https://api.sendcloud.net", - SendSMSAPI: "https://api.sendcloud.net/smsapi/send", + SendSMSAPI: "/smsapi/send", MaxReceivers: 100, SmsUser: smsUser, SmsKey: smsKey, }, nil } +// SendCloudSms represents the data required to send an SMS message. type SendCloudSms struct { TemplateId int MsgType int @@ -56,17 +59,18 @@ type SendCloudSms struct { Vars map[string]string } -func NewSendCloudSms(templateId, msgType int, phone []string, vars map[string]string) (SendCloudSms, error) { - if templateId == 0 { - return SendCloudSms{}, errors.New("templateId cannot be zero") +// NewSendCloudSms creates a new SendCloudSms instance with the provided parameters. +func NewSendCloudSms(templateId, msgType int, phone []string, vars map[string]string) (*SendCloudSms, error) { + if templateId <= 0 { + return nil, errors.New("templateId must be greater than zero") } if msgType < 0 { - return SendCloudSms{}, errors.New("msgType cannot be negative") + return nil, errors.New("msgType cannot be negative") } if len(phone) == 0 { - return SendCloudSms{}, errors.New("phone cannot be empty") + return nil, errors.New("phone cannot be empty") } - return SendCloudSms{ + return &SendCloudSms{ TemplateId: templateId, MsgType: msgType, Phone: phone, @@ -74,28 +78,18 @@ func NewSendCloudSms(templateId, msgType int, phone []string, vars map[string]st }, nil } +// SendMessage sends an SMS using SendCloud API. func SendMessage(sms SendCloudSms, config Config) error { - - if err := ValidateSendCloudSms(sms); err != nil { - return err + if err := validateSendCloudSms(sms); err != nil { + return fmt.Errorf("failed to send message: %w", err) } - if err := ValidateConfig(config); err != nil { - return err + if err := validateConfig(config); err != nil { + return fmt.Errorf("failed to send message: %w", err) } - params := url.Values{} - params.Set("smsUser", config.SmsUser) - params.Set("msgType", strconv.Itoa(sms.MsgType)) - params.Set("phone", strings.Join(sms.Phone, ",")) - params.Set("templateId", strconv.Itoa(sms.TemplateId)) - params.Set("timestamp", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)) - - if len(sms.Vars) > 0 { - varsJSON, err := json.Marshal(sms.Vars) - if err != nil { - return fmt.Errorf("failed to marshal vars: %v", err) - } - params.Set("vars", string(varsJSON)) + params, err := prepareParams(sms, config) + if err != nil { + return fmt.Errorf("failed to send message: %w", err) } signature := calculateSignature(params, config.SmsKey) @@ -103,68 +97,95 @@ func SendMessage(sms SendCloudSms, config Config) error { resp, err := http.PostForm(config.SendSMSAPI, params) if err != nil { - return fmt.Errorf("failed to send HTTP POST request: %v", err) + return fmt.Errorf("failed to send HTTP POST request: %w", err) } defer func() { - if err := resp.Body.Close(); err != nil { - log.Printf("error closing response body: %v", err) + err := resp.Body.Close() + if err != nil { + fmt.Println(err) + return } }() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("HTTP POST request failed with status code %d", resp.StatusCode) + } + body, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("failed to read response body: %v", err) + return fmt.Errorf("failed to read response body: %w", err) } var responseData ResponseData if err := json.Unmarshal(body, &responseData); err != nil { - return fmt.Errorf("failed to unmarshal response body: %v", err) + return fmt.Errorf("failed to unmarshal response body: %w", err) + } + + if !responseData.Result { + return fmt.Errorf("SMS sending failed: %s", responseData.Message) } return nil } -func ValidateConfig(config Config) error { - if config.CharSet == "" { - return errors.New("charSet cannot be empty") +// prepareParams prepares parameters for sending SMS. +func prepareParams(sms SendCloudSms, config Config) (url.Values, error) { + params := url.Values{} + params.Set("smsUser", config.SmsUser) + params.Set("msgType", strconv.Itoa(sms.MsgType)) + params.Set("phone", strings.Join(sms.Phone, ",")) + params.Set("templateId", strconv.Itoa(sms.TemplateId)) + params.Set("timestamp", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)) + + if len(sms.Vars) > 0 { + varsJSON, err := json.Marshal(sms.Vars) + if err != nil { + return nil, fmt.Errorf("failed to marshal vars: %v", err) + } + params.Set("vars", string(varsJSON)) } - if config.Server == "" { + + return params, nil +} + +// validateConfig validates the SMS sending configuration. +func validateConfig(config Config) error { + switch { + case config.CharSet == "": + return errors.New("charSet cannot be empty") + case config.Server == "": return errors.New("server cannot be empty") - } - if config.SendSMSAPI == "" { + case config.SendSMSAPI == "": return errors.New("sendSMSAPI cannot be empty") - } - if config.SmsUser == "" { + case config.SmsUser == "": return errors.New("smsUser cannot be empty") - } - if config.SmsKey == "" { + case config.SmsKey == "": return errors.New("smsKey cannot be empty") - } - if config.MaxReceivers <= 0 { + case config.MaxReceivers <= 0: return errors.New("maxReceivers must be greater than zero") } + return nil } -func ValidateSendCloudSms(sms SendCloudSms) error { - if sms.TemplateId == 0 { +// validateSendCloudSms validates the SendCloudSms data. +func validateSendCloudSms(sms SendCloudSms) error { + switch { + case sms.TemplateId == 0: return errors.New("templateId cannot be zero") - } - if sms.MsgType < 0 { + case sms.MsgType < 0: return errors.New("msgType cannot be negative") - } - if len(sms.Phone) == 0 { + case len(sms.Phone) == 0: return errors.New("phone cannot be empty") } return nil } +// calculateSignature calculates the signature for the request. func calculateSignature(params url.Values, key string) string { - sortedParams := params.Encode() - signStr := sortedParams + key - hasher := md5.New() + signStr := sortedParams + "&key=" + key + hasher := sha256.New() hasher.Write([]byte(signStr)) - signature := hex.EncodeToString(hasher.Sum(nil)) - return signature + return hex.EncodeToString(hasher.Sum(nil)) }