diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..ea9cf19 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,24 @@ +name: Go + +on: + push: + branches: [ master main ] + pull_request: + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.18 + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..44f2f1c --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,44 @@ +name: golangci-lint +on: + push: + tags: + - v* + branches: + - master + - main + pull_request: +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: latest + + # Optional: working directory, useful for monorepos + # working-directory: somedir + + # Optional: golangci-lint command line arguments. + # args: --issues-exit-code=0 + + # Optional: show only new issues if it's a pull request. The default value is `false`. + # only-new-issues: true + + # Optional: if set to true then the all caching functionality will be complete disabled, + # takes precedence over all other caching options. + # skip-cache: true + + # Optional: if set to true then the action don't cache or restore ~/go/pkg. + # skip-pkg-cache: true + + # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. + # skip-build-cache: true diff --git a/.gitignore b/.gitignore index 5cc3fec..e009763 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ gin-bin bin config.yaml .idea +.vscode diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..8e6c9ce --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,89 @@ +run: + modules-download-mode: readonly + issues-exit-code: 0 + +linters-settings: + exhaustive: + default-signifies-exhaustive: false + gci: + sections: + - standard + - default + - prefix(github.com/messagebird/sachet) + goconst: + min-occurrences: 5 + ignore-tests: true + godot: + scope: toplevel + exclude: + - go-sumtype:decl + - check interfaces + capital: true + godox: + keywords: + - BUG + - FIXME + - HACK + goimports: + local-prefixes: github.com/messagebird/sachet + gosimple: + go: "1.18" + checks: ["all"] + lll: + line-length: 130 + tab-width: 4 + nolintlint: + allow-unused: false + allow-leading-space: false + allow-no-explanation: [] + require-explanation: true + require-specific: true + staticcheck: + go: "1.18" + checks: ["all"] + stylecheck: + go: "1.18" + checks: ["all"] + dot-import-whitelist: [] + initialisms: [] + http-status-code-whitelist: [] + unparam: + check-exported: true + unused: + go: "1.18" + whitespace: + multi-if: false + multi-func: false + tagliatelle: + +linters: + enable-all: true + disable: + # TODO: enable one by one + - ireturn + - forbidigo + - containedctx + - tagliatelle + - cyclop + - errcheck + - exhaustivestruct + - forcetypeassert + - funlen + - gochecknoglobals + - gochecknoinits + - goerr113 + - gomnd + - gosec + - revive + - varnamelen + - wrapcheck + - noctx + - staticcheck + - stylecheck + - golint + - interfacer + - maligned + - nlreturn + - scopelint + - testpackage + - wsl diff --git a/Dockerfile b/Dockerfile index 667b4a7..05c6305 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17 AS builder +FROM golang:1.18 AS builder WORKDIR /build @@ -6,7 +6,7 @@ COPY . . RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -mod vendor -o sachet github.com/messagebird/sachet/cmd/sachet -FROM alpine +FROM alpine:3.15 COPY --from=builder /build/sachet /usr/local/bin COPY --chown=nobody examples/config.yaml /etc/sachet/config.yaml diff --git a/README.md b/README.md index 3d3a6d6..ea675ca 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Sachet -[![Build Status](https://travis-ci.org/messagebird/sachet.svg?branch=master)](https://travis-ci.org/messagebird/sachet) - Sachet (or सचेत) is Hindi for conscious. Sachet is an SMS alerting tool for the [Prometheus Alertmanager](https://github.com/prometheus/alertmanager). ## The problem diff --git a/cmd/sachet/config.go b/cmd/sachet/config.go index 258331a..747702a 100644 --- a/cmd/sachet/config.go +++ b/cmd/sachet/config.go @@ -3,11 +3,13 @@ package main import ( "io/ioutil" - "github.com/messagebird/sachet/provider/esendex" + "github.com/prometheus/alertmanager/template" + "gopkg.in/yaml.v2" "github.com/messagebird/sachet/provider/aliyun" "github.com/messagebird/sachet/provider/aspsms" "github.com/messagebird/sachet/provider/cm" + "github.com/messagebird/sachet/provider/esendex" "github.com/messagebird/sachet/provider/exotel" "github.com/messagebird/sachet/provider/freemobile" "github.com/messagebird/sachet/provider/ghasedak" @@ -32,9 +34,6 @@ import ( "github.com/messagebird/sachet/provider/textmagic" "github.com/messagebird/sachet/provider/turbosms" "github.com/messagebird/sachet/provider/twilio" - - "github.com/prometheus/alertmanager/template" - "gopkg.in/yaml.v2" ) type ReceiverConf struct { diff --git a/cmd/sachet/handlers.go b/cmd/sachet/handlers.go new file mode 100644 index 0000000..cafe170 --- /dev/null +++ b/cmd/sachet/handlers.go @@ -0,0 +1,110 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "sort" + "strings" + + "github.com/prometheus/alertmanager/template" + + "github.com/messagebird/sachet" +) + +type handlers struct{} + +func newAlertText(data template.Data) string { + if len(data.Alerts) > 1 { + labelAlerts := map[string]template.Alerts{ + "Firing": data.Alerts.Firing(), + "Resolved": data.Alerts.Resolved(), + } + text := "" + for label, alerts := range labelAlerts { + if len(alerts) > 0 { + text += label + ": \n" + for _, alert := range alerts { + text += alert.Labels["alertname"] + " @" + alert.Labels["instance"] + if len(alert.Labels["exported_instance"]) > 0 { + text += " (" + alert.Labels["exported_instance"] + ")" + } + text += "\n" + } + } + } + return text + } + + if len(data.Alerts) == 1 { + alert := data.Alerts[0] + tuples := []string{} + for k, v := range alert.Labels { + tuples = append(tuples, k+"= "+v) + } + sort.Strings(tuples) + return strings.ToUpper(data.Status) + " \n" + strings.Join(tuples, "\n") + } + + return "Alert \n" + strings.Join(data.CommonLabels.Values(), " | ") +} + +func (h handlers) Alert(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + // https://godoc.org/github.com/prometheus/alertmanager/template#Data + data := template.Data{} + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { + errorHandler(w, http.StatusBadRequest, err, "?") + return + } + + receiverConf := receiverConfByReceiver(data.Receiver) + if receiverConf == nil { + errorHandler(w, http.StatusBadRequest, fmt.Errorf("Receiver missing: %s", data.Receiver), "?") + return + } + provider, err := providerByName(receiverConf.Provider) + if err != nil { + errorHandler(w, http.StatusInternalServerError, err, receiverConf.Provider) + return + } + + var text string + if receiverConf.Text != "" { + text, err = tmpl.ExecuteTextString(receiverConf.Text, data) + if err != nil { + errorHandler(w, http.StatusInternalServerError, err, receiverConf.Provider) + return + } + } else { + text = newAlertText(data) + } + + message := sachet.Message{ + To: receiverConf.To, + From: receiverConf.From, + Type: receiverConf.Type, + Text: text, + } + + if err = provider.Send(message); err != nil { + errorHandler(w, http.StatusBadRequest, err, receiverConf.Provider) + return + } + + requestTotal.WithLabelValues("200", receiverConf.Provider).Inc() +} + +func (h handlers) Reload(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + if r.Method == http.MethodPost { + log.Println("Loading configuration file", *configFile) + if err := LoadConfig(*configFile); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } else { + http.Error(w, "Invalid request method.", http.StatusMethodNotAllowed) + } +} diff --git a/cmd/sachet/handlers_test.go b/cmd/sachet/handlers_test.go new file mode 100644 index 0000000..5fe3dfd --- /dev/null +++ b/cmd/sachet/handlers_test.go @@ -0,0 +1,62 @@ +package main + +import ( + "testing" + + "github.com/prometheus/alertmanager/template" + "github.com/stretchr/testify/assert" +) + +func Test_newAlertText(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + data template.Data + exp string + }{ + { + name: "empty", + data: template.Data{}, + exp: "Alert \n", + }, + { + name: "empty alerts", + data: template.Data{ + Alerts: template.Alerts{ + template.Alert{}, + }, + }, + exp: " \n", + }, + { + name: "alert labels", + data: template.Data{ + Alerts: template.Alerts{ + template.Alert{ + Labels: map[string]string{ + "alertname": "a", + "instance": "a", + "exported_instance": "a", + }, + }, + }, + }, + exp: " \nalertname= a\nexported_instance= a\ninstance= a", + }, + { + name: "common labels", + data: template.Data{ + CommonLabels: template.KV{ + "a": "a", + "b": "b", + "c": "c", + }, + }, + exp: "Alert \na | b | c", + }, + } + for _, tc := range cases { + assert.Equal(t, tc.exp, newAlertText(tc.data), tc.name) + } +} diff --git a/cmd/sachet/main.go b/cmd/sachet/main.go index 0a2856f..60d9b9f 100644 --- a/cmd/sachet/main.go +++ b/cmd/sachet/main.go @@ -8,16 +8,16 @@ import ( "net/http" "os" "strconv" - "strings" - "github.com/messagebird/sachet/provider/esendex" - - "github.com/messagebird/sachet/provider/tencentcloud" + "github.com/heptiolabs/healthcheck" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/messagebird/sachet" "github.com/messagebird/sachet/provider/aliyun" "github.com/messagebird/sachet/provider/aspsms" "github.com/messagebird/sachet/provider/cm" + "github.com/messagebird/sachet/provider/esendex" "github.com/messagebird/sachet/provider/exotel" "github.com/messagebird/sachet/provider/freemobile" "github.com/messagebird/sachet/provider/ghasedak" @@ -38,15 +38,10 @@ import ( "github.com/messagebird/sachet/provider/sms77" "github.com/messagebird/sachet/provider/smsc" "github.com/messagebird/sachet/provider/telegram" + "github.com/messagebird/sachet/provider/tencentcloud" "github.com/messagebird/sachet/provider/textmagic" "github.com/messagebird/sachet/provider/turbosms" "github.com/messagebird/sachet/provider/twilio" - - "github.com/prometheus/alertmanager/template" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - - "github.com/heptiolabs/healthcheck" ) var ( @@ -62,92 +57,11 @@ func main() { log.Fatalf("Error loading configuration: %s", err) } - http.HandleFunc("/alert", func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - - // https://godoc.org/github.com/prometheus/alertmanager/template#Data - data := template.Data{} - if err := json.NewDecoder(r.Body).Decode(&data); err != nil { - errorHandler(w, http.StatusBadRequest, err, "?") - return - } - - receiverConf := receiverConfByReceiver(data.Receiver) - if receiverConf == nil { - errorHandler(w, http.StatusBadRequest, fmt.Errorf("Receiver missing: %s", data.Receiver), "?") - return - } - provider, err := providerByName(receiverConf.Provider) - if err != nil { - errorHandler(w, http.StatusInternalServerError, err, receiverConf.Provider) - return - } - - var text string - if receiverConf.Text != "" { - text, err = tmpl.ExecuteTextString(receiverConf.Text, data) - if err != nil { - errorHandler(w, http.StatusInternalServerError, err, receiverConf.Provider) - return - } - } else { - if len(data.Alerts) > 1 { - labelAlerts := map[string]template.Alerts{ - "Firing": data.Alerts.Firing(), - "Resolved": data.Alerts.Resolved(), - } - for label, alerts := range labelAlerts { - if len(alerts) > 0 { - text += label + ": \n" - for _, alert := range alerts { - text += alert.Labels["alertname"] + " @" + alert.Labels["instance"] - if len(alert.Labels["exported_instance"]) > 0 { - text += " (" + alert.Labels["exported_instance"] + ")" - } - text += "\n" - } - } - } - } else if len(data.Alerts) == 1 { - alert := data.Alerts[0] - tuples := []string{} - for k, v := range alert.Labels { - tuples = append(tuples, k+"= "+v) - } - text = strings.ToUpper(data.Status) + " \n" + strings.Join(tuples, "\n") - } else { - text = "Alert \n" + strings.Join(data.CommonLabels.Values(), " | ") - } - } - - message := sachet.Message{ - To: receiverConf.To, - From: receiverConf.From, - Type: receiverConf.Type, - Text: text, - } - - if err = provider.Send(message); err != nil { - errorHandler(w, http.StatusBadRequest, err, receiverConf.Provider) - return - } - - requestTotal.WithLabelValues("200", receiverConf.Provider).Inc() - }) + app := handlers{} + http.HandleFunc("/alert", app.Alert) http.Handle("/metrics", promhttp.Handler()) - - http.HandleFunc("/-/reload", func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - if r.Method == "POST" { - log.Println("Loading configuration file", *configFile) - if err := LoadConfig(*configFile); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - } else { - http.Error(w, "Invalid request method.", http.StatusMethodNotAllowed) - } - }) + http.HandleFunc("/-/reload", app.Reload) hc := healthcheck.NewMetricsHandler(prometheus.DefaultRegisterer, "sachet") @@ -163,7 +77,7 @@ func main() { log.Fatal(http.ListenAndServe(*listenAddress, nil)) } -// receiverConfByReceiver loops the receiver conf list and returns the first instance with that name +// receiverConfByReceiver loops the receiver conf list and returns the first instance with that name. func receiverConfByReceiver(name string) *ReceiverConf { for i := range config.Receivers { rc := &config.Receivers[i] @@ -175,6 +89,7 @@ func receiverConfByReceiver(name string) *ReceiverConf { } func providerByName(name string) (sachet.Provider, error) { + // TODO: use map of providers instead switch name { case "messagebird": return messagebird.NewMessageBird(config.Providers.MessageBird), nil @@ -219,7 +134,7 @@ func providerByName(name string) (sachet.Provider, error) { case "ovh": return ovh.NewOvh(config.Providers.OVH) case "tencentcloud": - return tencentcloud.NewTencentCloud(config.Providers.TencentCloud) + return tencentcloud.NewTencentCloud(config.Providers.TencentCloud), nil case "sap": return sap.NewSap(config.Providers.Sap), nil case "esendex": @@ -250,10 +165,15 @@ func errorHandler(w http.ResponseWriter, status int, err error, provider string) err.Error(), } // respond json - bytes, _ := json.Marshal(data) - json := string(bytes[:]) - fmt.Fprint(w, json) + body, err := json.Marshal(data) + if err != nil { + log.Fatalf("marshalling error: " + err.Error()) + } + + if _, err := w.Write(body); err != nil { + log.Fatalf("marshalling error: " + err.Error()) + } - log.Println("Error: " + json) + log.Println("error: " + string(body)) requestTotal.WithLabelValues(strconv.FormatInt(int64(status), 10), provider).Inc() } diff --git a/cmd/sachet/main_test.go b/cmd/sachet/main_test.go index e144f63..0c7a388 100644 --- a/cmd/sachet/main_test.go +++ b/cmd/sachet/main_test.go @@ -1,6 +1,22 @@ package main -import "testing" +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" -func TestPlaceholder(t *testing.T) { + "github.com/stretchr/testify/assert" +) + +func Test_errorHandler(t *testing.T) { + t.Parallel() + + var ( + w = httptest.NewRecorder() + expect = `{"Error":true,"Status":403,"Message":"access forbidden"}` + err = errors.New("access forbidden") + ) + errorHandler(w, http.StatusForbidden, err, "test") + assert.Equal(t, expect, w.Body.String()) } diff --git a/cmd/sachet/telemetry.go b/cmd/sachet/telemetry.go index 77ca46f..ebc303c 100644 --- a/cmd/sachet/telemetry.go +++ b/cmd/sachet/telemetry.go @@ -2,14 +2,12 @@ package main import "github.com/prometheus/client_golang/prometheus" -var ( - requestTotal = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Name: "sachet_requests_total", - Help: "How many requests processed, partitioned by status code and provider.", - }, - []string{"code", "provider"}, - ) +var requestTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "sachet_requests_total", + Help: "How many requests processed, partitioned by status code and provider.", + }, + []string{"code", "provider"}, ) func init() { diff --git a/provider/aliyun/aliyun.go b/provider/aliyun/aliyun.go index d66bbb2..34a8d67 100644 --- a/provider/aliyun/aliyun.go +++ b/provider/aliyun/aliyun.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi" + "github.com/messagebird/sachet" ) @@ -19,6 +20,8 @@ type Config struct { TemplateParamKey string `yaml:"template_param_key"` } +var _ (sachet.Provider) = (*Aliyun)(nil) + type Aliyun struct { client *dysmsapi.Client config *Config @@ -36,7 +39,6 @@ func NewAliyun(config Config) (*Aliyun, error) { } func (aliyun *Aliyun) Send(message sachet.Message) error { - var err error = nil switch message.Type { case "", "text": request := dysmsapi.CreateSendSmsRequest() @@ -52,11 +54,12 @@ func (aliyun *Aliyun) Send(message sachet.Message) error { var response *dysmsapi.SendSmsResponse response, err = aliyun.client.SendSms(request) if err == nil && (!response.IsSuccess() || response.Code != "OK") { - err = fmt.Errorf(response.String()) + return fmt.Errorf(response.String()) } } default: return fmt.Errorf("unknown message type %s", message.Type) } - return err + + return nil } diff --git a/provider/aspsms/aspsms.go b/provider/aspsms/aspsms.go index 8fafed7..e91805a 100644 --- a/provider/aspsms/aspsms.go +++ b/provider/aspsms/aspsms.go @@ -11,20 +11,22 @@ import ( "github.com/messagebird/sachet" ) -// Config is the configuration struct for AspSms provider +// Config is the configuration struct for AspSms provider. type Config struct { Username string `yaml:"username"` Password string `yaml:"password"` } -// AspSms contains the necessary values for the AspSms provider +var _ (sachet.Provider) = (*AspSms)(nil) + +// AspSms contains the necessary values for the AspSms provider. type AspSms struct { Config httpClient *http.Client } -// NewAspSms creates and returns a new AspSms struct +// NewAspSms creates and returns a new AspSms struct. func NewAspSms(config Config) *AspSms { return &AspSms{ config, @@ -42,7 +44,7 @@ type requestPayload struct { const apiUrl = "https://json.aspsms.com/SendSimpleTextSMS" -// Send sends SMS to user registered in configuration +// Send sends SMS to user registered in configuration. func (c *AspSms) Send(message sachet.Message) error { params := requestPayload{ Username: c.Username, diff --git a/provider/cm/cm.go b/provider/cm/cm.go index 92c3f61..f1c6604 100644 --- a/provider/cm/cm.go +++ b/provider/cm/cm.go @@ -10,19 +10,21 @@ import ( "github.com/messagebird/sachet" ) -// Config is the configuration struct for CM provider +// Config is the configuration struct for CM provider. type Config struct { ProductToken string `yaml:"producttoken"` } -// CM contains the necessary values for the CM provider +var _ (sachet.Provider) = (*CM)(nil) + +// CM contains the necessary values for the CM provider. type CM struct { Config } var cmHTTPClient = &http.Client{Timeout: time.Second * 20} -// NewCM creates and returns a new CM struct +// NewCM creates and returns a new CM struct. func NewCM(config Config) *CM { return &CM{config} } @@ -48,7 +50,7 @@ type CMPayload struct { } `json:"messages"` } -// Send sends SMS to n number of people using Bulk SMS API +// Send sends SMS to n number of people using Bulk SMS API. func (c *CM) Send(message sachet.Message) error { smsURL := "https://gw.cmtelecom.com/v1.0/message" @@ -87,7 +89,10 @@ func (c *CM) Send(message sachet.Message) error { defer response.Body.Close() var body []byte - response.Body.Read(body) + _, err = response.Body.Read(body) + if err != nil { + return err + } if response.StatusCode == http.StatusOK && err == nil { return nil } diff --git a/provider/esendex/esendex.go b/provider/esendex/esendex.go index 061ee2a..dfaf484 100644 --- a/provider/esendex/esendex.go +++ b/provider/esendex/esendex.go @@ -6,10 +6,11 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/messagebird/sachet" "io/ioutil" "net/http" "time" + + "github.com/messagebird/sachet" ) const ( @@ -23,6 +24,8 @@ type Config struct { AccountReference string `yaml:"account_reference"` } +var _ (sachet.Provider) = (*Esendex)(nil) + type Esendex struct { Config httpClient *http.Client @@ -40,7 +43,7 @@ func (e *Esendex) Send(message sachet.Message) (err error) { err = e.sendOne(message, phoneNumber) if err != nil { - return fmt.Errorf("failed to make API call to Esendex: %s", err) + return fmt.Errorf("failed to make API call to Esendex: %w", err) } } @@ -81,7 +84,6 @@ func (e *Esendex) sendOne(message sachet.Message, phoneNumber string) (err error } data, err := json.Marshal(params) - if err != nil { return err } @@ -98,7 +100,6 @@ func (e *Esendex) sendOne(message sachet.Message, phoneNumber string) (err error request.SetBasicAuth(e.Config.User, e.Config.ApiToken) response, err := e.httpClient.Do(request) - if err != nil { return err } diff --git a/provider/exotel/exotel.go b/provider/exotel/exotel.go index ea782f1..bd0aed5 100644 --- a/provider/exotel/exotel.go +++ b/provider/exotel/exotel.go @@ -10,28 +10,30 @@ import ( "github.com/messagebird/sachet" ) -//Config configuration struct for exotel Client +// Config configuration struct for exotel Client. type Config struct { AccountSID string `yaml:"account_sid"` AuthToken string `yaml:"auth_token"` } -//ExotelRequestTimeout is the timeout for http request to exotel +// ExotelRequestTimeout is the timeout for http request to exotel. const ExotelRequestTimeout = time.Second * 20 -//Exotel is the exte Exotel +var _ (sachet.Provider) = (*Exotel)(nil) + +// Exotel is the exte Exotel. type Exotel struct { AccountSid string Token string } -//NewExotel creates a new +// NewExotel creates a new. func NewExotel(config Config) *Exotel { Exotel := &Exotel{AccountSid: config.AccountSID, Token: config.AuthToken} return Exotel } -//Send send sms to n number of people using bulk sms api +// Send send sms to n number of people using bulk sms api. func (c *Exotel) Send(message sachet.Message) (err error) { smsURL := fmt.Sprintf("https://twilix.exotel.in/v1/Accounts/%s/Sms/send.json", c.AccountSid) var request *http.Request @@ -39,7 +41,7 @@ func (c *Exotel) Send(message sachet.Message) (err error) { form := url.Values{"From": {message.From}, "Body": {message.Text}, "To": message.To} - //preparing the request + // preparing the request. request, err = http.NewRequest("POST", smsURL, strings.NewReader(form.Encode())) if err != nil { return @@ -48,7 +50,8 @@ func (c *Exotel) Send(message sachet.Message) (err error) { request.SetBasicAuth(c.AccountSid, c.Token) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.Header.Set("User-Agent", "SachetV1.0") - //calling the endpoint + + // calling the endpoint. httpClient := &http.Client{} httpClient.Timeout = ExotelRequestTimeout @@ -58,7 +61,10 @@ func (c *Exotel) Send(message sachet.Message) (err error) { } defer resp.Body.Close() var body []byte - resp.Body.Read(body) + _, err = resp.Body.Read(body) + if err != nil { + return err + } if resp.StatusCode == http.StatusOK && err == nil { return } diff --git a/provider/freemobile/freemobile.go b/provider/freemobile/freemobile.go index 41204f6..06f2791 100644 --- a/provider/freemobile/freemobile.go +++ b/provider/freemobile/freemobile.go @@ -10,21 +10,23 @@ import ( "github.com/messagebird/sachet" ) -// Config is the configuration struct for FreeMobile provider +// Config is the configuration struct for FreeMobile provider. type Config struct { Username string `yaml:"username"` Password string `yaml:"password"` URL string `yaml:"url"` } -// FreeMobile contains the necessary values for the FreeMobile provider +var _ (sachet.Provider) = (*FreeMobile)(nil) + +// FreeMobile contains the necessary values for the FreeMobile provider. type FreeMobile struct { Config } var freemobileHTTPClient = &http.Client{Timeout: time.Second * 20} -// NewFreeMobile creates and returns a new FreeMobile struct +// NewFreeMobile creates and returns a new FreeMobile struct. func NewFreeMobile(config Config) *FreeMobile { if config.URL == "" { config.URL = "https://smsapi.free-mobile.fr/sendmsg" @@ -38,7 +40,7 @@ type payload struct { Message string `json:"msg"` } -// Send sends SMS to user registered in configuration +// Send sends SMS to user registered in configuration. func (c *FreeMobile) Send(message sachet.Message) error { params := payload{ User: c.Username, @@ -62,6 +64,7 @@ func (c *FreeMobile) Send(message sachet.Message) error { if err != nil { return err } + defer response.Body.Close() if response.StatusCode == http.StatusOK && err == nil { return nil diff --git a/provider/ghasedak/ghasedak.go b/provider/ghasedak/ghasedak.go index 717ee91..4898561 100644 --- a/provider/ghasedak/ghasedak.go +++ b/provider/ghasedak/ghasedak.go @@ -1,63 +1,65 @@ -package ghasedak - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strings" - "time" - - "github.com/messagebird/sachet" -) - -// Retrieving required data from 'ghasedak' sections of config.yaml -type Config struct { - APIToken string `yaml:"api_token"` -} - -// Creating the KaveNegar to contain provider data -type Ghasedak struct { - Config - HTTPClient *http.Client // The HTTP client to send requests on -} - -// Ghasedak creates and returns a new Ghasedak struct -func NewGhasedak(config Config) *Ghasedak { - return &Ghasedak{ - config, - &http.Client{Timeout: time.Second * 20}, - } -} - -// Building the API and call the Ghasedak endpoint to send SMS to the configured receptor from config.yaml -func (ns *Ghasedak) Send(message sachet.Message) error { - endpoint := "https://api.ghasedak.me/v2/sms/send/pair" - data := url.Values{} - data.Set("message", message.Text) - data.Set("receptor", strings.Join(message.To, ",")) - request, err := http.NewRequest("POST", endpoint, strings.NewReader(data.Encode())) - if err != nil { - return err - } - request.Header.Set("User-Agent", "Sachet") - request.Header.Add("content-type", "application/x-www-form-urlencoded") - request.Header.Add("apikey", ns.APIToken) - request.Header.Add("cache-control", "no-cache") - response, err := ns.HTTPClient.Do(request) - if err != nil { - return err - } - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(response.Body) - return fmt.Errorf( - "SMS sending failed. HTTP status code: %d, Response body: %s", - response.StatusCode, - body, - ) - } - fmt.Println("Message sent: ", message.Text) - return nil -} +package ghasedak + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/messagebird/sachet" +) + +// Retrieving required data from 'ghasedak' sections of config.yaml. +type Config struct { + APIToken string `yaml:"api_token"` +} + +var _ (sachet.Provider) = (*Ghasedak)(nil) + +// Creating the KaveNegar to contain provider data. +type Ghasedak struct { + Config + HTTPClient *http.Client // The HTTP client to send requests on. +} + +// Ghasedak creates and returns a new Ghasedak struct. +func NewGhasedak(config Config) *Ghasedak { + return &Ghasedak{ + config, + &http.Client{Timeout: time.Second * 20}, + } +} + +// Building the API and call the Ghasedak endpoint to send SMS to the configured receptor from config.yaml. +func (ns *Ghasedak) Send(message sachet.Message) error { + endpoint := "https://api.ghasedak.me/v2/sms/send/pair" + data := url.Values{} + data.Set("message", message.Text) + data.Set("receptor", strings.Join(message.To, ",")) + request, err := http.NewRequest("POST", endpoint, strings.NewReader(data.Encode())) + if err != nil { + return err + } + request.Header.Set("User-Agent", "Sachet") + request.Header.Add("content-type", "application/x-www-form-urlencoded") + request.Header.Add("apikey", ns.APIToken) + request.Header.Add("cache-control", "no-cache") + response, err := ns.HTTPClient.Do(request) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + body, _ := ioutil.ReadAll(response.Body) + return fmt.Errorf( + "SMS sending failed. HTTP status code: %d, Response body: %s", + response.StatusCode, + body, + ) + } + fmt.Println("Message sent: ", message.Text) + return nil +} diff --git a/provider/infobip/infobip.go b/provider/infobip/infobip.go index 41afae1..ca4b05a 100644 --- a/provider/infobip/infobip.go +++ b/provider/infobip/infobip.go @@ -10,16 +10,18 @@ import ( "github.com/messagebird/sachet" ) -//Config configuration struct for Infobip Client +// Config configuration struct for Infobip Client. type Config struct { Token string `yaml:"token"` Secret string `yaml:"secret"` } -//InfobipRequestTimeout is the timeout for http request to Infobip +// InfobipRequestTimeout is the timeout for http request to Infobip. const InfobipRequestTimeout = time.Second * 20 -//Infobip is the exte Infobip +var _ (sachet.Provider) = (*Infobip)(nil) + +// Infobip is the exte Infobip. type Infobip struct { Config } @@ -38,16 +40,16 @@ type InfobipPayload struct { Messages []InfobipMessage `json:"messages"` } -//NewInfobip creates a new +// NewInfobip creates a new. func NewInfobip(config Config) *Infobip { Infobip := &Infobip{config} return Infobip } -//Send send sms to n number of people using bulk sms api +// Send send sms to n number of people using bulk sms api. func (c *Infobip) Send(message sachet.Message) (err error) { smsURL := "https://api.infobip.com/sms/2/text/advanced" - //smsURL = "http://requestb.in/pwf2ufpw" + // smsURL = "http://requestb.in/pwf2ufpw" var request *http.Request var resp *http.Response @@ -70,7 +72,7 @@ func (c *Infobip) Send(message sachet.Message) (err error) { return err } - //preparing the request + // preparing the request. request, err = http.NewRequest("POST", smsURL, bytes.NewBuffer(data)) if err != nil { return @@ -79,7 +81,7 @@ func (c *Infobip) Send(message sachet.Message) (err error) { request.SetBasicAuth(c.Token, c.Secret) request.Header.Set("Content-Type", "application/json") request.Header.Set("User-Agent", "SachetV1.0") - //calling the endpoint + // calling the endpoint. httpClient := &http.Client{} httpClient.Timeout = InfobipRequestTimeout @@ -89,7 +91,10 @@ func (c *Infobip) Send(message sachet.Message) (err error) { } defer resp.Body.Close() var body []byte - resp.Body.Read(body) + _, err = resp.Body.Read(body) + if err != nil { + return + } if resp.StatusCode == http.StatusOK && err == nil { return } diff --git a/provider/kannel/kannel.go b/provider/kannel/kannel.go index da98ff3..b633710 100644 --- a/provider/kannel/kannel.go +++ b/provider/kannel/kannel.go @@ -9,31 +9,39 @@ import ( "github.com/messagebird/sachet" ) -//Config configuration struct for Kannel Client +// Config configuration struct for Kannel Client. type Config struct { URL string `yaml:"url"` User string `yaml:"username"` Pass string `yaml:"password"` } -//KannelRequestTimeout is the timeout for http request to Kannel +// KannelRequestTimeout is the timeout for http request to Kannel. const KannelRequestTimeout = time.Second * 20 -//Kannel is the exte Kannel +var _ (sachet.Provider) = (*Kannel)(nil) + +// Kannel is the exte Kannel. type Kannel struct { Config } -//NewKannel creates a new +// NewKannel creates a new. func NewKannel(config Config) *Kannel { Kannel := &Kannel{config} return Kannel } -//Send send sms to n number of people using bulk sms api -func (c *Kannel) Send(message sachet.Message) (err error) { +// Send send sms to n number of people using bulk sms api. +func (c *Kannel) Send(message sachet.Message) error { for _, recipient := range message.To { - queryParams := url.Values{"from": {message.From}, "to": {recipient}, "text": {message.Text}, "user": {c.User}, "pass": {c.Pass}} + queryParams := url.Values{ + "from": {message.From}, + "to": {recipient}, + "text": {message.Text}, + "user": {c.User}, + "pass": {c.Pass}, + } request, err := http.NewRequest("GET", c.URL, nil) if err != nil { @@ -52,10 +60,12 @@ func (c *Kannel) Send(message sachet.Message) (err error) { if err != nil { return err } + defer response.Body.Close() - if response.StatusCode >= 400 { + if response.StatusCode >= http.StatusBadRequest { return fmt.Errorf("Failed sending sms. statusCode: %d", response.StatusCode) } } - return + + return nil } diff --git a/provider/kavenegar/kavenegar.go b/provider/kavenegar/kavenegar.go index 48289aa..8322623 100644 --- a/provider/kavenegar/kavenegar.go +++ b/provider/kavenegar/kavenegar.go @@ -10,19 +10,21 @@ import ( "github.com/messagebird/sachet" ) -// Retrieving required data from 'kavenegar' sections of config.yaml +// Retrieving required data from 'kavenegar' sections of config.yaml. type Config struct { APIToken string `yaml:"api_token"` PhoneNumbers []string `yaml:"phone_numbers"` } -// Creating the KaveNegar to contain provider data +var _ (sachet.Provider) = (*KaveNegar)(nil) + +// Creating the KaveNegar to contain provider data. type KaveNegar struct { Config - HTTPClient *http.Client // The HTTP client to send requests on + HTTPClient *http.Client // The HTTP client to send requests on. } -// KaveNegar creates and returns a new KaveNegar struct +// KaveNegar creates and returns a new KaveNegar struct. func NewKaveNegar(config Config) *KaveNegar { return &KaveNegar{ config, @@ -30,7 +32,7 @@ func NewKaveNegar(config Config) *KaveNegar { } } -// Building the API and call the KaveNegar endpoint to send SMS to the configured receptor from config.yaml +// Building the API and call the KaveNegar endpoint to send SMS to the configured receptor from config.yaml. func (ns *KaveNegar) Send(message sachet.Message) error { url := "https://api.kavenegar.com/v1/" + ns.APIToken + "/sms/send.json" request, err := http.NewRequest("GET", url, nil) @@ -40,7 +42,8 @@ func (ns *KaveNegar) Send(message sachet.Message) error { request.Header.Set("User-Agent", "Sachet") params := request.URL.Query() params.Add("receptor", strings.Join(message.To, ",")) - // "params.Add("sender", message.From)" retrieves the sender number using "from" under receivers section, if you leave that empty, KaveNegar will use default sender SMS number to send the message + // "params.Add("sender", message.From)" retrieves the sender number using "from" under receivers section, + // if you leave that empty, KaveNegar will use default sender SMS number to send the message. params.Add("sender", message.From) params.Add("message", message.Text) request.URL.RawQuery = params.Encode() diff --git a/provider/mailruim/mailruim.go b/provider/mailruim/mailruim.go index b4da8eb..5c4e463 100644 --- a/provider/mailruim/mailruim.go +++ b/provider/mailruim/mailruim.go @@ -2,6 +2,7 @@ package mailruim import ( botgolang "github.com/mail-ru-im/bot-golang" + "github.com/messagebird/sachet" ) @@ -10,6 +11,8 @@ type Config struct { Url string `yaml:"url"` } +var _ (sachet.Provider) = (*MailruIM)(nil) + type MailruIM struct { bot *botgolang.Bot } @@ -28,7 +31,9 @@ func NewMailruIM(config Config) (*MailruIM, error) { func (mr *MailruIM) Send(message sachet.Message) error { for _, ChatID := range message.To { msg := mr.bot.NewTextMessage(ChatID, message.Text) - msg.Send() + if err := msg.Send(); err != nil { + // TODO: handle the error + } } return nil } diff --git a/provider/mediaburst/mediaburst.go b/provider/mediaburst/mediaburst.go index 6c8ce53..771a63d 100644 --- a/provider/mediaburst/mediaburst.go +++ b/provider/mediaburst/mediaburst.go @@ -10,26 +10,28 @@ import ( "github.com/messagebird/sachet" ) -//Config configuration struct for mediaburst Client +// Config configuration struct for mediaburst Client. type Config struct { APIKey string `yaml:"api_key"` } -//MediaBurstRequestTimeout is the timeout for http request to mediaburst +// MediaBurstRequestTimeout is the timeout for http request to mediaburst. const MediaBurstRequestTimeout = time.Second * 20 -//MediaBurst is the exte MediaBurst +var _ (sachet.Provider) = (*MediaBurst)(nil) + +// MediaBurst is the exte MediaBurst. type MediaBurst struct { Config } -//NewMediaBurst creates a new +// NewMediaBurst creates a new. func NewMediaBurst(config Config) *MediaBurst { MediaBurst := &MediaBurst{config} return MediaBurst } -//Send send sms to n number of people using bulk sms api +// Send send sms to n number of people using bulk sms api. func (c *MediaBurst) Send(message sachet.Message) (err error) { smsURL := "https://api.clockworksms.com/http/send.aspx" var request *http.Request @@ -37,7 +39,7 @@ func (c *MediaBurst) Send(message sachet.Message) (err error) { form := url.Values{"Key": {c.APIKey}, "From": {message.From}, "Content": {message.Text}, "To": message.To} - //preparing the request + // preparing the request. request, err = http.NewRequest("GET", smsURL, strings.NewReader(form.Encode())) if err != nil { return @@ -45,7 +47,7 @@ func (c *MediaBurst) Send(message sachet.Message) (err error) { request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.Header.Set("User-Agent", "SachetV1.0") - //calling the endpoint + // calling the endpoint. httpClient := &http.Client{} httpClient.Timeout = MediaBurstRequestTimeout @@ -55,7 +57,10 @@ func (c *MediaBurst) Send(message sachet.Message) (err error) { } defer resp.Body.Close() var body []byte - resp.Body.Read(body) + _, err = resp.Body.Read(body) + if err != nil { + return + } if resp.StatusCode == http.StatusOK && err == nil { return } diff --git a/provider/messagebird/messagebird.go b/provider/messagebird/messagebird.go index b5dc528..7d45320 100644 --- a/provider/messagebird/messagebird.go +++ b/provider/messagebird/messagebird.go @@ -8,6 +8,7 @@ import ( messagebird "github.com/messagebird/go-rest-api" sms "github.com/messagebird/go-rest-api/sms" voicemessage "github.com/messagebird/go-rest-api/voicemessage" + "github.com/messagebird/sachet" ) @@ -20,6 +21,8 @@ type Config struct { Repeat int `yaml:"repeat"` } +var _ (sachet.Provider) = (*MessageBird)(nil) + type MessageBird struct { client *messagebird.Client messageParams sms.Params @@ -44,8 +47,7 @@ func NewMessageBird(config Config) *MessageBird { } } -func (mb *MessageBird) Send(message sachet.Message) error { - var err error = nil +func (mb *MessageBird) Send(message sachet.Message) (err error) { switch message.Type { case "", "text": _, err = sms.Create(mb.client, message.From, message.To, message.Text, &mb.messageParams) diff --git a/provider/nexmo/nexmo.go b/provider/nexmo/nexmo.go index 0b1f54f..f0ba83a 100644 --- a/provider/nexmo/nexmo.go +++ b/provider/nexmo/nexmo.go @@ -1,8 +1,9 @@ package nexmo import ( + nexmo "gopkg.in/njern/gonexmo.v1" + "github.com/messagebird/sachet" - "gopkg.in/njern/gonexmo.v1" ) type Config struct { @@ -10,6 +11,8 @@ type Config struct { APISecret string `yaml:"api_secret"` } +var _ (sachet.Provider) = (*Nexmo)(nil) + type Nexmo struct { client *nexmo.Client } diff --git a/provider/nowsms/nowsms.go b/provider/nowsms/nowsms.go index 840edf8..e94df32 100644 --- a/provider/nowsms/nowsms.go +++ b/provider/nowsms/nowsms.go @@ -10,20 +10,22 @@ import ( "github.com/messagebird/sachet" ) -// Config is the configuration struct for NowSms provider +// Config is the configuration struct for NowSms provider. type Config struct { User string `yaml:"username"` Password string `yaml:"password"` PhoneNumbers []string `yaml:"phone_numbers"` } -// NowSms contains the necessary values for the NowSms provider +var _ (sachet.Provider) = (*NowSms)(nil) + +// NowSms contains the necessary values for the NowSms provider. type NowSms struct { Config - HTTPClient *http.Client // The HTTP client to send requests on + HTTPClient *http.Client // The HTTP client to send requests on. } -// NewNowSms creates and returns a new NowSms struct +// NewNowSms creates and returns a new NowSms struct. func NewNowSms(config Config) *NowSms { return &NowSms{ config, @@ -31,7 +33,7 @@ func NewNowSms(config Config) *NowSms { } } -// Send sends SMS to user registered in configuration +// Send sends SMS to user registered in configuration. func (ns *NowSms) Send(message sachet.Message) error { const nowSmsURL = "http://sms-gateway:8800/send" diff --git a/provider/otc/otc.go b/provider/otc/otc.go index cab3af4..1a90986 100644 --- a/provider/otc/otc.go +++ b/provider/otc/otc.go @@ -5,11 +5,12 @@ import ( "crypto/tls" "encoding/json" "fmt" - "github.com/messagebird/sachet" "io" "io/ioutil" "net/http" "time" + + "github.com/messagebird/sachet" ) type Config struct { @@ -20,8 +21,8 @@ type Config struct { Password string `yaml:"password"` ProjectID string `yaml:"project_id"` Insecure bool `yaml:"insecure"` - token string - otcBaseURL string + Token string `yaml:"-"` + OtcBaseURL string `yaml:"-"` } type smsRequest struct { @@ -107,20 +108,20 @@ func (c *OTC) loginRequest() error { tr := http.DefaultTransport.(*http.Transport) tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: c.Insecure} - client := &http.Client{Timeout: time.Duration(10 * time.Second), Transport: tr} + client := &http.Client{Timeout: 10 * time.Second, Transport: tr} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() - if resp.StatusCode >= 400 { + if resp.StatusCode >= http.StatusBadRequest { return fmt.Errorf("OTC API request failed with HTTP status code %d", resp.StatusCode) } - c.token = resp.Header.Get("X-Subject-Token") + c.Token = resp.Header.Get("X-Subject-Token") - if c.token == "" { + if c.Token == "" { return fmt.Errorf("unable to get auth token") } @@ -146,17 +147,17 @@ func (c *OTC) loginRequest() error { for _, v := range endpointResp.Token.Catalog { if v.Type == "smn" { for _, endpoint := range v.Endpoints { - c.otcBaseURL = fmt.Sprintf("%s%s", endpoint.URL, c.ProjectID) + c.OtcBaseURL = fmt.Sprintf("%s%s", endpoint.URL, c.ProjectID) continue } } - if c.otcBaseURL != "" { + if c.OtcBaseURL != "" { continue } } - if c.otcBaseURL == "" { + if c.OtcBaseURL == "" { return fmt.Errorf("unable to find snm endpoint") } @@ -164,15 +165,14 @@ func (c *OTC) loginRequest() error { } func (c *OTC) SendRequest(method, resource string, payload *smsRequest, attempts int) (io.Reader, error) { - if len(c.token) == 0 { + if len(c.Token) == 0 { err := c.loginRequest() - if err != nil { return nil, err } } - url := fmt.Sprintf("%s/%s", c.otcBaseURL, resource) + url := fmt.Sprintf("%s/%s", c.OtcBaseURL, resource) body, err := json.Marshal(payload) if err != nil { return nil, err @@ -183,13 +183,13 @@ func (c *OTC) SendRequest(method, resource string, payload *smsRequest, attempts return nil, err } req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Auth-Token", c.token) + req.Header.Set("X-Auth-Token", c.Token) tr := http.DefaultTransport.(*http.Transport) tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: c.Insecure} client := &http.Client{ - Timeout: time.Duration(10 * time.Second), + Timeout: 10 * time.Second, Transport: tr, } resp, err := client.Do(req) @@ -198,15 +198,14 @@ func (c *OTC) SendRequest(method, resource string, payload *smsRequest, attempts } defer resp.Body.Close() - if resp.StatusCode == 401 { - // Set empty token to force login - c.token = "" + if resp.StatusCode == http.StatusUnauthorized { + // Set empty token to force login. + c.Token = "" if attempts--; attempts > 0 { return c.SendRequest(method, resource, payload, attempts) - } else { - return nil, err } - } else if resp.StatusCode >= 400 { + return nil, err + } else if resp.StatusCode >= http.StatusBadRequest { return nil, fmt.Errorf("OTC API request %s failed with HTTP status code %d", url, resp.StatusCode) } @@ -218,10 +217,9 @@ func (c *OTC) SendRequest(method, resource string, payload *smsRequest, attempts return bytes.NewReader(body1), nil } -//Send send sms to n number of people using bulk sms api +// Send send sms to n number of people using bulk sms api. func (c *OTC) Send(message sachet.Message) (err error) { for _, recipent := range message.To { - r1 := &smsRequest{ Endpoint: recipent, Message: message.Text, diff --git a/provider/ovh/ovh.go b/provider/ovh/ovh.go index 85fc0ed..2ca4416 100644 --- a/provider/ovh/ovh.go +++ b/provider/ovh/ovh.go @@ -3,8 +3,9 @@ package ovh import ( "fmt" - "github.com/messagebird/sachet" "github.com/ovh/go-ovh/ovh" + + "github.com/messagebird/sachet" ) type Config struct { @@ -19,6 +20,8 @@ type Config struct { Priority string `yaml:"priority"` } +var _ (sachet.Provider) = (*Ovh)(nil) + type Ovh struct { client *ovh.Client config *Config diff --git a/provider/pushbullet/pushbullet.go b/provider/pushbullet/pushbullet.go index 0851eaf..57fecfb 100644 --- a/provider/pushbullet/pushbullet.go +++ b/provider/pushbullet/pushbullet.go @@ -4,8 +4,9 @@ import ( "fmt" "strings" - "github.com/messagebird/sachet" "github.com/xconstruct/go-pushbullet" + + "github.com/messagebird/sachet" ) const ( @@ -13,30 +14,30 @@ const ( channelTargetType = "channel" ) -// Config is the configuration struct for the Pushbullet provider +// Config is the configuration struct for the Pushbullet provider. type Config struct { AccessToken string `yaml:"access_token"` } -// Pushbullet contains the necessary values for the Pushbullet provider +var _ (sachet.Provider) = (*Pushbullet)(nil) + +// Pushbullet contains the necessary values for the Pushbullet provider. type Pushbullet struct { Config } -// NewPushbullet creates and returns a new Pushbullet struct +// NewPushbullet creates and returns a new Pushbullet struct. func NewPushbullet(config Config) *Pushbullet { return &Pushbullet{config} } -// Send pushes a note to devices registered in configuration +// Send pushes a note to devices registered in configuration. func (c *Pushbullet) Send(message sachet.Message) error { - for _, recipient := range message.To { - - // create pushbullet client + // create pushbullet client. pb := pushbullet.New(c.AccessToken) - // parse recipient + // parse recipient. targetTypeName := strings.SplitN(recipient, ":", 2) if len(targetTypeName) != 2 { return fmt.Errorf("cannot parse recipient %s: expecting targetType:targetName", recipient) @@ -72,7 +73,6 @@ func (c *Pushbullet) Send(message sachet.Message) error { default: return fmt.Errorf("unrecognised target type: %s", targetType) } - } return nil diff --git a/provider/sap/sap.go b/provider/sap/sap.go index 11c9b9f..b86a9f2 100644 --- a/provider/sap/sap.go +++ b/provider/sap/sap.go @@ -2,25 +2,28 @@ package sap import ( "fmt" - "github.com/messagebird/sachet" "net/http" "strings" "time" + + "github.com/messagebird/sachet" ) -// Config is the configuration struct for Sap provider +// Config is the configuration struct for Sap provider. type Config struct { URL string `yaml:"url"` AuthHash string `yaml:"auth_hash"` } -// Sap contains the necessary values for the Sap provider +var _ (sachet.Provider) = (*Sap)(nil) + +// Sap contains the necessary values for the Sap provider. type Sap struct { Config - HTTPClient *http.Client // The HTTP client to send requests on + HTTPClient *http.Client // The HTTP client to send requests on. } -// NewSap creates and returns a new Sap struct +// NewSap creates and returns a new Sap struct. func NewSap(config Config) *Sap { if config.URL == "" { config.URL = "https://sms-pp.sapmobileservices.com/cmn/xxxxxxxxxx/xxxxxxxxxxx.sms" @@ -31,12 +34,12 @@ func NewSap(config Config) *Sap { } } -// Send sends SMS to user registered in configuration +// Send sends SMS to user registered in configuration. func (c *Sap) Send(message sachet.Message) error { - - // No \n in Text tolerated + // No \n in Text tolerated. msg := strings.ReplaceAll(message.Text, "\n", " - ") - content := fmt.Sprintf("Version=2.0\nSubject=Alert\n[MSISDN]\nList=%s\n[MESSAGE]\nText=%s\n[SETUP]\nSplitText=yes\n[END]", strings.Join(message.To, ","), msg) + content := fmt.Sprintf("Version=2.0\nSubject=Alert\n[MSISDN]\nList=%s\n[MESSAGE]\nText=%s\n[SETUP]\nSplitText=yes\n[END]", + strings.Join(message.To, ","), msg) request, err := http.NewRequest("POST", c.URL, strings.NewReader(content)) if err != nil { @@ -48,6 +51,7 @@ func (c *Sap) Send(message sachet.Message) error { if err != nil { return err } + defer response.Body.Close() if response.StatusCode == http.StatusOK && err == nil { return nil diff --git a/provider/sipgate/sipgate.go b/provider/sipgate/sipgate.go index c58304a..6ea27b8 100644 --- a/provider/sipgate/sipgate.go +++ b/provider/sipgate/sipgate.go @@ -14,18 +14,20 @@ const sipgateURL = "https://api.sipgate.com/v2/sessions/sms" var sipgateHTTPClient = &http.Client{Timeout: time.Second * 20} -// Config is the configuration struct for Sipgate provider +// Config is the configuration struct for Sipgate provider. type Config struct { Username string `yaml:"username"` Password string `yaml:"password"` } -// Sipgate contains the necessary values for the Sipgate provider +var _ (sachet.Provider) = (*Sipgate)(nil) + +// Sipgate contains the necessary values for the Sipgate provider. type Sipgate struct { Config } -// NewSipgate creates and returns a new Sipgate struct +// NewSipgate creates and returns a new Sipgate struct. func NewSipgate(config Config) *Sipgate { return &Sipgate{config} } @@ -36,7 +38,7 @@ type payload struct { Message string `json:"message"` } -// Send sends SMS to user registered in configuration +// Send sends SMS to user registered in configuration. func (c *Sipgate) Send(message sachet.Message) error { for _, recipient := range message.To { params := payload{ @@ -63,6 +65,7 @@ func (c *Sipgate) Send(message sachet.Message) error { if err != nil { return err } + defer response.Body.Close() if response.StatusCode != http.StatusNoContent { return fmt.Errorf("Failed sending sms. statusCode: %d", response.StatusCode) diff --git a/provider/sms77/sms77.go b/provider/sms77/sms77.go index 8e7315b..2c5ee76 100644 --- a/provider/sms77/sms77.go +++ b/provider/sms77/sms77.go @@ -4,23 +4,26 @@ import ( "fmt" "strings" - "github.com/messagebird/sachet" "github.com/sms77io/go-client/sms77api" + + "github.com/messagebird/sachet" ) -// Config is the configuration struct for Sms77 provider +// Config is the configuration struct for Sms77 provider. type Config struct { ApiKey string `yaml:"api_key"` Debug bool `yaml:"debug"` } -// Sms77 contains the necessary values for the Sms77 provider +var _ (sachet.Provider) = (*Sms77)(nil) + +// Sms77 contains the necessary values for the Sms77 provider. type Sms77 struct { client *sms77api.Sms77API config Config } -// NewSms77 creates and returns a new Sms77 struct +// NewSms77 creates and returns a new Sms77 struct. func NewSms77(config Config) *Sms77 { client := sms77api.New(sms77api.Options{ ApiKey: config.ApiKey, @@ -34,7 +37,7 @@ func NewSms77(config Config) *Sms77 { } } -// Send sends SMS to user registered in configuration +// Send sends SMS to user registered in configuration. func (s77 *Sms77) Send(message sachet.Message) error { var err error = nil switch message.Type { diff --git a/provider/smsc/smsc.go b/provider/smsc/smsc.go index f8c9382..bfe73c3 100644 --- a/provider/smsc/smsc.go +++ b/provider/smsc/smsc.go @@ -2,10 +2,11 @@ package smsc import ( "fmt" - "github.com/messagebird/sachet" "net/http" "net/url" "time" + + "github.com/messagebird/sachet" ) type Config struct { @@ -15,6 +16,8 @@ type Config struct { const SmscRequestTimeout = time.Second * 60 +var _ (sachet.Provider) = (*Smsc)(nil) + type Smsc struct { Login string Password string @@ -29,15 +32,16 @@ func (c *Smsc) Send(message sachet.Message) (err error) { for _, number := range message.To { err = c.SendOne(message, number) if err != nil { - return fmt.Errorf("Failed to make API call to smsc:%s", err) + return fmt.Errorf("Failed to make API call to smsc: %w", err) } } return } -func (c *Smsc) SendOne(message sachet.Message, PhoneNumber string) (err error) { - encoded_message := url.QueryEscape(message.Text) - smsURL := fmt.Sprintf("https://smsc.ru/sys/send.php?login=%s&psw=%s&phones=%s&sender=%s&fmt=0&mes=%s", c.Login, c.Password, PhoneNumber, message.From, encoded_message) +func (c *Smsc) SendOne(message sachet.Message, phoneNumber string) (err error) { + encodedMessage := url.QueryEscape(message.Text) + smsURL := fmt.Sprintf("https://smsc.ru/sys/send.php?login=%s&psw=%s&phones=%s&sender=%s&fmt=0&mes=%s", + c.Login, c.Password, phoneNumber, message.From, encodedMessage) var request *http.Request var resp *http.Response request, err = http.NewRequest("GET", smsURL, nil) @@ -52,7 +56,10 @@ func (c *Smsc) SendOne(message sachet.Message, PhoneNumber string) (err error) { } defer resp.Body.Close() var body []byte - resp.Body.Read(body) + _, err = resp.Body.Read(body) + if err != nil { + return + } if resp.StatusCode == http.StatusOK && err == nil { return } diff --git a/provider/telegram/telegram.go b/provider/telegram/telegram.go index 2d5ec59..7f260bb 100644 --- a/provider/telegram/telegram.go +++ b/provider/telegram/telegram.go @@ -1,9 +1,11 @@ package telegram import ( - "github.com/messagebird/sachet" - "gopkg.in/telegram-bot-api.v4" "strconv" + + tgbotapi "gopkg.in/telegram-bot-api.v4" + + "github.com/messagebird/sachet" ) type Config struct { @@ -12,6 +14,8 @@ type Config struct { DisableWebPagePreview bool `yaml:"disable_web_page_preview"` } +var _ (sachet.Provider) = (*Telegram)(nil) + type Telegram struct { bot *tgbotapi.BotAPI config *Config diff --git a/provider/tencentcloud/tencentcloud.go b/provider/tencentcloud/tencentcloud.go index 59c4a6a..956e4df 100644 --- a/provider/tencentcloud/tencentcloud.go +++ b/provider/tencentcloud/tencentcloud.go @@ -2,12 +2,15 @@ package tencentcloud import ( "encoding/json" + "errors" "fmt" - "github.com/messagebird/sachet" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" - "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" + tcError "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20190711" + + "github.com/messagebird/sachet" ) type Config struct { @@ -21,12 +24,14 @@ type Config struct { Truncate bool `yaml:"truncate"` } +var _ (sachet.Provider) = (*TencentCloud)(nil) + type TencentCloud struct { client *sms.Client config *Config } -func NewTencentCloud(config Config) (*TencentCloud, error) { +func NewTencentCloud(config Config) *TencentCloud { credential := common.NewCredential( config.SecretId, config.SecretKey, @@ -39,8 +44,7 @@ func NewTencentCloud(config Config) (*TencentCloud, error) { return &TencentCloud{ client: client, config: &config, - }, nil - + } } func truncateString(str string, num int) string { @@ -55,7 +59,6 @@ func truncateString(str string, num int) string { } func (tencentcloud *TencentCloud) Send(message sachet.Message) error { - var err error = nil switch message.Type { case "", "text": request := sms.NewSendSmsRequest() @@ -70,13 +73,19 @@ func (tencentcloud *TencentCloud) Send(message sachet.Message) error { request.TemplateID = common.StringPtr(tencentcloud.config.TemplateCode) request.PhoneNumberSet = common.StringPtrs(message.To) response, err := tencentcloud.client.SendSms(request) - if _, ok := err.(*errors.TencentCloudSDKError); ok { + + var errTencentCloudSDKError *tcError.TencentCloudSDKError + if errors.As(err, &errTencentCloudSDKError) { fmt.Printf("An API error has returned: %s", err) return err } - b, _ := json.Marshal(response.Response) + + b, err := json.Marshal(response.Response) + if err != nil { + return err + } fmt.Printf("%s", b) } - return err + return nil } diff --git a/provider/textmagic/textmagic.go b/provider/textmagic/textmagic.go index 63a8bd4..1b397bc 100644 --- a/provider/textmagic/textmagic.go +++ b/provider/textmagic/textmagic.go @@ -5,8 +5,9 @@ import ( "fmt" "strings" - "github.com/messagebird/sachet" textmagic "github.com/textmagic/textmagic-rest-go-v2/v2" + + "github.com/messagebird/sachet" ) type Config struct { @@ -14,6 +15,8 @@ type Config struct { APIKey string `yaml:"api_key"` } +var _ (sachet.Provider) = (*TextMagic)(nil) + type TextMagic struct { client *textmagic.APIClient auth context.Context @@ -33,11 +36,10 @@ func NewTextMagic(config Config) *TextMagic { } } -func (tm *TextMagic) Send(message sachet.Message) error { - var err error = nil +func (tm *TextMagic) Send(message sachet.Message) (err error) { switch message.Type { case "", "text": - joinedTo := strings.Join(message.To[:], ",") + joinedTo := strings.Join(message.To, ",") _, _, err = tm.client.TextMagicApi.SendMessage(tm.auth, textmagic.SendMessageInputObject{ Text: message.Text, Phones: joinedTo, diff --git a/provider/turbosms/turbosms.go b/provider/turbosms/turbosms.go index 4c2060a..cfdb07e 100644 --- a/provider/turbosms/turbosms.go +++ b/provider/turbosms/turbosms.go @@ -13,11 +13,14 @@ import ( "github.com/messagebird/sachet" ) -// sachet section +// sachet section. type Config struct { Alogin string `yaml:"login"` Apassword string `yaml:"password"` } + +var _ (sachet.Provider) = (*Turbosms)(nil) + type Turbosms struct { Login string Password string @@ -28,7 +31,7 @@ func NewTurbosms(config Config) *Turbosms { return Turbosms } -// http url for turbosms +// http url for turbosms. var urlSoap string = "http://turbosms.in.ua/api/soap.html" type SoapBody struct { @@ -75,7 +78,11 @@ func SoapEncode(contents interface{}) ([]byte, error) { return nil, err } data = append([]byte("\n"), data...) - env := SoapEnvelopeReqest{Id1: "http://schemas.xmlsoap.org/soap/envelope/", Id2: "http://turbosms.in.ua/api/Turbo", Body: SoapBody{Contents: data}} + env := SoapEnvelopeReqest{ + Id1: "http://schemas.xmlsoap.org/soap/envelope/", + Id2: "http://turbosms.in.ua/api/Turbo", + Body: SoapBody{Contents: data}, + } return xml.MarshalIndent(&env, "", " ") } @@ -89,12 +96,12 @@ func SoapDecode(data []byte, contents interface{}) error { } func Request(c *http.Client, url string, payload []byte) ([]byte, error, int) { - resp, err := c.Post(url, "text/xml", bytes.NewBuffer(payload)) statuscode := resp.StatusCode if err != nil { return nil, err, statuscode } + defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { @@ -115,7 +122,7 @@ func (c *Turbosms) Send(message sachet.Message) (err error) { Timeout: 15 * time.Second, Jar: cookieJar, } - reply, err, statusreply := Request(clientConfig, urlSoap, []byte(data)) + reply, err, statusreply := Request(clientConfig, urlSoap, data) if err != nil { return err } @@ -126,17 +133,16 @@ func (c *Turbosms) Send(message sachet.Message) (err error) { return err } // Request - replysms, err, statusreplysms := Request(clientConfig, urlSoap, []byte(datasms)) + replysms, err, statusreplysms := Request(clientConfig, urlSoap, datasms) if err != nil { return err - } if statusreply == 200 && statusreplysms == 200 && err == nil { return nil } var resp getAuthResponse - err = SoapDecode([]byte(reply), &resp) + err = SoapDecode(reply, &resp) if err != nil { return err } diff --git a/provider/twilio/twilio.go b/provider/twilio/twilio.go index d3d30a0..e595e6a 100644 --- a/provider/twilio/twilio.go +++ b/provider/twilio/twilio.go @@ -2,6 +2,7 @@ package twilio import ( "github.com/carlosdp/twiliogo" + "github.com/messagebird/sachet" ) @@ -10,6 +11,8 @@ type Config struct { AuthToken string `yaml:"auth_token"` } +var _ (sachet.Provider) = (*Twilio)(nil) + type Twilio struct { client twiliogo.Client }