github.com/blend/go-sdk@v1.20220411.3/slack/webhook_sender.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package slack 9 10 import ( 11 "context" 12 "encoding/json" 13 "io" 14 "net/http" 15 16 "github.com/blend/go-sdk/ex" 17 "github.com/blend/go-sdk/r2" 18 "github.com/blend/go-sdk/webutil" 19 ) 20 21 const ( 22 // ErrNon200 is the exception class when a non-200 is returned from slack. 23 ErrNon200 ex.Class = "slack; non-200 status code returned from remote" 24 ) 25 26 var ( 27 _ Sender = (*WebhookSender)(nil) 28 ) 29 30 // New creates a new webhook sender. 31 func New(cfg Config) *WebhookSender { 32 return &WebhookSender{ 33 Transport: new(http.Transport), 34 Config: cfg, 35 } 36 } 37 38 // WebhookSender sends slack webhooks. 39 type WebhookSender struct { 40 Transport *http.Transport 41 RequestDefaults []r2.Option 42 Config Config 43 } 44 45 // MessageDefaults returns default message options. 46 func (whs WebhookSender) MessageDefaults() []MessageOption { 47 return []MessageOption{ 48 OptMessageUsernameOrDefault(whs.Config.Username), 49 OptMessageChannelOrDefault(whs.Config.Channel), 50 OptMessageIconEmojiOrDefault(whs.Config.IconEmoji), 51 OptMessageIconURLOrDefault(whs.Config.IconURL), 52 } 53 } 54 55 func (whs WebhookSender) send(ctx context.Context, message Message) (*http.Response, error) { 56 message = ApplyMessageOptions(message, whs.MessageDefaults()...) 57 options := append(whs.RequestDefaults, 58 r2.OptPost(), 59 r2.OptTransport(whs.Transport), 60 r2.OptJSONBody(message), 61 r2.OptHeaderValue(webutil.HeaderContentType, "application/json"), 62 ) 63 return r2.New(whs.Config.Webhook, options...).Do() 64 } 65 66 // Send sends a slack hook. 67 func (whs WebhookSender) Send(ctx context.Context, message Message) error { 68 res, err := whs.send(ctx, message) 69 if err != nil { 70 return err 71 } 72 defer res.Body.Close() 73 74 if statusCode := res.StatusCode; statusCode < http.StatusOK || statusCode > 299 { 75 contents, _ := io.ReadAll(res.Body) 76 return ex.New(ErrNon200, ex.OptMessage(string(contents))) 77 } 78 return nil 79 } 80 81 // SendAndReadResponse sends a slack hook and returns the deserialized response 82 func (whs WebhookSender) SendAndReadResponse(ctx context.Context, message Message) (*PostMessageResponse, error) { 83 res, err := whs.send(ctx, message) 84 if err != nil { 85 return nil, err 86 } 87 defer res.Body.Close() 88 89 var contents PostMessageResponse 90 err = json.NewDecoder(res.Body).Decode(&contents) 91 if err != nil { 92 return nil, ex.New(err) 93 } 94 if statusCode := res.StatusCode; statusCode < http.StatusOK || statusCode > 299 { 95 return &contents, ex.New(ErrNon200, ex.OptMessagef("%#v", contents)) 96 } 97 return &contents, nil 98 } 99 100 // PostMessage posts a basic message to a given channel. 101 func (whs WebhookSender) PostMessage(channel, messageText string, options ...MessageOption) error { 102 message := Message{ 103 Channel: channel, 104 Text: messageText, 105 } 106 for _, option := range options { 107 option(&message) 108 } 109 return whs.Send(context.Background(), message) 110 } 111 112 // PostMessageAndReadResponse posts a basic message to a given channel and returns the deserialized response 113 func (whs WebhookSender) PostMessageAndReadResponse(channel, messageText string, options ...MessageOption) (*PostMessageResponse, error) { 114 message := Message{ 115 Channel: channel, 116 Text: messageText, 117 } 118 for _, option := range options { 119 option(&message) 120 } 121 return whs.SendAndReadResponse(context.Background(), message) 122 } 123 124 // PostMessageContext posts a basic message to a given chanel with a given context. 125 func (whs WebhookSender) PostMessageContext(ctx context.Context, channel, messageText string, options ...MessageOption) error { 126 message := Message{ 127 Channel: channel, 128 Text: messageText, 129 } 130 for _, option := range options { 131 option(&message) 132 } 133 return whs.Send(ctx, message) 134 } 135 136 // PostMessageAndReadResponseContext posts a basic message to a given channel and returns the deserialized response 137 func (whs WebhookSender) PostMessageAndReadResponseContext(ctx context.Context, channel, messageText string, options ...MessageOption) (*PostMessageResponse, error) { 138 message := Message{ 139 Channel: channel, 140 Text: messageText, 141 } 142 for _, option := range options { 143 option(&message) 144 } 145 return whs.SendAndReadResponse(ctx, message) 146 }