From 75e0b57b85a46c8120263b8904c5954852fbf0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= <nils@piksel.se> Date: Sat, 3 Jul 2021 21:43:41 +0200 Subject: [PATCH 1/6] feat(slack): add bot API support --- pkg/services/slack/slack.go | 80 +++++++++--- pkg/services/slack/slack_config.go | 39 +++--- pkg/services/slack/slack_errors.go | 24 +--- pkg/services/slack/slack_json.go | 49 +++++-- pkg/services/slack/slack_test.go | 200 ++++++++++++++++++----------- pkg/services/slack/slack_token.go | 135 +++++++++++++------ 6 files changed, 347 insertions(+), 180 deletions(-) diff --git a/pkg/services/slack/slack.go b/pkg/services/slack/slack.go index 45e5aed6..24358402 100644 --- a/pkg/services/slack/slack.go +++ b/pkg/services/slack/slack.go @@ -2,11 +2,13 @@ package slack import ( "bytes" + "encoding/json" "fmt" "github.com/containrrr/shoutrrr/pkg/format" + "github.com/containrrr/shoutrrr/pkg/util/jsonclient" + "io/ioutil" "net/http" "net/url" - "strings" "github.com/containrrr/shoutrrr/pkg/services/standard" "github.com/containrrr/shoutrrr/pkg/types" @@ -20,7 +22,7 @@ type Service struct { } const ( - apiURL = "https://hooks.slack.com/services" + apiPostMessage = "https://slack.com/api/chat.postMessage" ) // Send a notification message to Slack @@ -31,11 +33,20 @@ func (service *Service) Send(message string, params *types.Params) error { return err } - if err := ValidateToken(config.Token); err != nil { - return err + payload := CreateJSONPayload(config, message) + + var err error + if config.Token.IsAPIToken() { + err = service.sendApi(config, payload) + } else { + err = service.sendWebhook(config, payload) + } + + if err != nil { + return fmt.Errorf("failed to send slack notification: %v", err) } - return service.doSend(config, message) + return nil } // Initialize loads ServiceConfig from configURL and sets logger for this Service @@ -43,33 +54,60 @@ func (service *Service) Initialize(configURL *url.URL, logger types.StdLogger) e service.Logger.SetLogger(logger) service.config = &Config{} service.pkr = format.NewPropKeyResolver(service.config) - return service.config.setURL(&service.pkr, configURL) + if err := service.config.setURL(&service.pkr, configURL); err != nil { + return err + } + return nil } -func (service *Service) doSend(config *Config, message string) error { - postURL := service.getURL(config) - payload, err := CreateJSONPayload(config, message) - - var res *http.Response - if err == nil { - res, err = http.Post(postURL, "application/json", bytes.NewBuffer(payload)) +func (service *Service) sendApi(config *Config, payload interface{}) error { + response := APIResponse{} + jsonClient := jsonclient.Client{ + HTTPClient: http.DefaultClient, + AuthorizationHeader: config.Token.Authorization(), } - if res == nil && err == nil { - err = fmt.Errorf("unknown error") + if err := jsonClient.Post(apiPostMessage, payload, &response); err != nil { + return err } - if err == nil && res.StatusCode != http.StatusOK { - err = fmt.Errorf("response status code %s", res.Status) + if !response.Ok { + if response.Error != "" { + return fmt.Errorf("api response: %v", response.Error) + } + return fmt.Errorf("unknown error") } - if err != nil { - return fmt.Errorf("failed to send slack notification: %v", err) + if response.Warning != "" { + service.Logger.Logf("Slack API warning: %q", response.Warning) } return nil } -func (service *Service) getURL(config *Config) string { - return fmt.Sprintf("%s/%s", apiURL, strings.Join(config.Token, "/")) +func (service *Service) sendWebhook(config *Config, payload interface{}) error { + payloadBytes, err := json.Marshal(payload) + var res *http.Response + res, err = http.Post(config.Token.WebhookURL(), jsonclient.ContentType, bytes.NewBuffer(payloadBytes)) + + if err != nil { + return fmt.Errorf("failed to invoke webhook: %v", err) + } + defer res.Body.Close() + resBytes, _ := ioutil.ReadAll(res.Body) + response := string(resBytes) + + switch response { + case "": + if res.StatusCode != http.StatusOK { + return fmt.Errorf("webhook status: %v", res.Status) + } + // Treat status 200 as no error regardless of actual content + fallthrough + case "ok": + return nil + default: + return fmt.Errorf("webhook response: %v", response) + } + } diff --git a/pkg/services/slack/slack_config.go b/pkg/services/slack/slack_config.go index 844e7a8b..54d26cc2 100644 --- a/pkg/services/slack/slack_config.go +++ b/pkg/services/slack/slack_config.go @@ -1,21 +1,21 @@ package slack import ( - "fmt" "github.com/containrrr/shoutrrr/pkg/format" "github.com/containrrr/shoutrrr/pkg/services/standard" "github.com/containrrr/shoutrrr/pkg/types" "net/url" - "strings" ) // Config for the slack service type Config struct { standard.EnumlessConfig - BotName string `default:"" optional:"" url:"user" desc:"Bot name (uses default if empty)"` - Token []string `desc:"Webhook token parts" url:"host,path1,path2"` - Color string `key:"color" optional:"" desc:"Message left-hand border color"` - Title string `key:"title" optional:"" desc:"Prepended text above the message"` + BotName string `optional:"uses bot default" key:"botname,username" desc:"Bot name"` + Icon string `key:"icon,icon_emoji,icon_url" default:"" optional:"" desc:"Use emoji or URL as icon (based on presence of http(s):// prefix)"` + Token Token `desc:"API Bot token" url:"user,pass"` + Color string `key:"color" optional:"default border color" desc:"Message left-hand border color"` + Title string `key:"title" optional:"omitted" desc:"Prepended text above the message"` + Channel string `url:"host" desc:"Channel to send messages to in Cxxxxxxxxxx format"` } // GetURL returns a URL representation of it's current field values @@ -32,9 +32,8 @@ func (config *Config) SetURL(url *url.URL) error { func (config *Config) getURL(resolver types.ConfigQueryResolver) *url.URL { return &url.URL{ - User: url.User(config.BotName), - Host: config.Token[0], - Path: fmt.Sprintf("/%s/%s", config.Token[1], config.Token[2]), + User: config.Token.UserInfo(), + Host: config.Channel, Scheme: Scheme, ForceQuery: false, RawQuery: format.BuildQuery(resolver), @@ -43,21 +42,21 @@ func (config *Config) getURL(resolver types.ConfigQueryResolver) *url.URL { func (config *Config) setURL(resolver types.ConfigQueryResolver, serviceURL *url.URL) error { - botName := serviceURL.User.Username() + var token string + var err error - host := serviceURL.Hostname() + if len(serviceURL.Path) > 1 { + // Reading legacy config URL format + token = serviceURL.Hostname() + serviceURL.Path - token := strings.Split(serviceURL.Path, "/") - token[0] = host - - if len(token) < 2 { - token = []string{"", "", ""} + config.Channel = "webhook" + config.BotName = serviceURL.User.Username() + } else { + token = serviceURL.User.String() + config.Channel = serviceURL.Hostname() } - config.BotName = botName - config.Token = token - - if err := ValidateToken(config.Token); err != nil { + if err = config.Token.SetFromProp(token); err != nil { return err } diff --git a/pkg/services/slack/slack_errors.go b/pkg/services/slack/slack_errors.go index c033947e..6adbc49a 100644 --- a/pkg/services/slack/slack_errors.go +++ b/pkg/services/slack/slack_errors.go @@ -1,21 +1,11 @@ package slack -// ErrorMessage for error events within the slack service -type ErrorMessage string +import "errors" -const ( - // TokenAMissing from the service URL - TokenAMissing ErrorMessage = "first part of the API token is missing" - // TokenBMissing from the service URL - TokenBMissing ErrorMessage = "second part of the API token is missing" - // TokenCMissing from the service URL - TokenCMissing ErrorMessage = "third part of the API token is missing." - // TokenAMalformed inthe service URL - TokenAMalformed ErrorMessage = "first part of the API token is malformed" - // TokenBMalformed inthe service URL - TokenBMalformed ErrorMessage = "second part of the API token is malformed" - // TokenCMalformed inthe service URL - TokenCMalformed ErrorMessage = "third part of the API token is malformed" - // NotEnoughArguments provided in the service URL - NotEnoughArguments ErrorMessage = "the apiURL does not include enough arguments" +var ( + // ErrorInvalidToken is returned whenever the specified token does not match any known formats + ErrorInvalidToken = errors.New("invalid slack token format") + + // ErrorMismatchedTokenSeparators is returned if the token uses different separators between parts (of the recognized `/-,`) + ErrorMismatchedTokenSeparators = errors.New("invalid webhook token format") ) diff --git a/pkg/services/slack/slack_json.go b/pkg/services/slack/slack_json.go index 2e3502c4..903e5801 100644 --- a/pkg/services/slack/slack_json.go +++ b/pkg/services/slack/slack_json.go @@ -1,16 +1,19 @@ package slack import ( - "encoding/json" + "regexp" "strings" ) -// JSON used within the Slack service -type JSON struct { +// messagePayload used within the Slack service +type messagePayload struct { Text string `json:"text"` BotName string `json:"username,omitempty"` Blocks []block `json:"blocks,omitempty"` Attachments []attachment `json:"attachments,omitempty"` + Channel string `json:"channel,omitempty"` + IconEmoji string `json:"icon_emoji,omitempty"` + IconURL string `json:"icon_url,omitempty"` } type block struct { @@ -39,8 +42,19 @@ type legacyField struct { Short bool `json:"short,omitempty"` } -// CreateJSONPayload compatible with the slack webhook api -func CreateJSONPayload(config *Config, message string) ([]byte, error) { +type APIResponse struct { + Ok bool `json:"ok"` + Error string `json:"error"` + Warning string `json:"warning"` + MetaData struct { + Warnings []string `json:"warnings"` + } `json:"response_metadata"` +} + +var iconUrlPattern = regexp.MustCompile(`https?://`) + +// CreateJSONPayload compatible with the slack post message API +func CreateJSONPayload(config *Config, message string) interface{} { var atts []attachment for _, line := range strings.Split(message, "\n") { @@ -50,10 +64,23 @@ func CreateJSONPayload(config *Config, message string) ([]byte, error) { }) } - return json.Marshal( - JSON{ - Text: config.Title, - BotName: config.BotName, - Attachments: atts, - }) + payload := messagePayload{ + Text: config.Title, + BotName: config.BotName, + Attachments: atts, + } + + if config.Icon != "" { + if iconUrlPattern.MatchString(config.Icon) { + payload.IconURL = config.Icon + } else { + payload.IconEmoji = config.Icon + } + } + + if config.Channel != "webhook" { + payload.Channel = config.Channel + } + + return payload } diff --git a/pkg/services/slack/slack_test.go b/pkg/services/slack/slack_test.go index e855fab2..c47190f4 100644 --- a/pkg/services/slack/slack_test.go +++ b/pkg/services/slack/slack_test.go @@ -13,9 +13,11 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + gomegaformat "github.com/onsi/gomega/format" ) func TestSlack(t *testing.T) { + gomegaformat.CharactersAroundMismatchToInclude = 20 RegisterFailHandler(Fail) RunSpecs(t, "Shoutrrr Slack Suite") } @@ -49,76 +51,57 @@ var _ = Describe("the slack service", func() { }) }) + // xoxb:123456789012-1234567890123-4mt0t4l1YL3g1T5L4cK70k3N + When("given a token with a malformed part", func() { It("should return an error if part A is not 9 letters", func() { - slackURL, err := url.Parse("slack://lol@12345678/123456789/123456789123456789123456") - Expect(err).NotTo(HaveOccurred()) - expectErrorMessageGivenURL( - TokenAMalformed, - slackURL, - ) + expectErrorMessageGivenURL(ErrorInvalidToken, "slack://lol@12345678/123456789/123456789123456789123456") }) It("should return an error if part B is not 9 letters", func() { - slackURL, err := url.Parse("slack://lol@123456789/12345678/123456789123456789123456") - Expect(err).NotTo(HaveOccurred()) - expectErrorMessageGivenURL( - TokenBMalformed, - slackURL, - ) + expectErrorMessageGivenURL(ErrorInvalidToken, "slack://lol@123456789/12345678/123456789123456789123456") }) It("should return an error if part C is not 24 letters", func() { - slackURL, err := url.Parse("slack://123456789/123456789/12345678912345678912345") - Expect(err).NotTo(HaveOccurred()) - expectErrorMessageGivenURL( - TokenCMalformed, - slackURL, - ) + expectErrorMessageGivenURL(ErrorInvalidToken, "slack://123456789/123456789/12345678912345678912345") }) }) When("given a token missing a part", func() { It("should return an error if the missing part is A", func() { - slackURL, err := url.Parse("slack://lol@/123456789/123456789123456789123456") - Expect(err).NotTo(HaveOccurred()) - expectErrorMessageGivenURL( - TokenAMissing, - slackURL, - ) + expectErrorMessageGivenURL(ErrorInvalidToken, "slack://lol@/123456789/123456789123456789123456") }) It("should return an error if the missing part is B", func() { - slackURL, err := url.Parse("slack://lol@123456789//123456789") - Expect(err).NotTo(HaveOccurred()) - expectErrorMessageGivenURL( - TokenBMissing, - slackURL, - ) - + expectErrorMessageGivenURL(ErrorInvalidToken, "slack://lol@123456789//123456789") }) It("should return an error if the missing part is C", func() { - slackURL, err := url.Parse("slack://lol@123456789/123456789/") - Expect(err).NotTo(HaveOccurred()) - expectErrorMessageGivenURL( - TokenCMissing, - slackURL, - ) + expectErrorMessageGivenURL(ErrorInvalidToken, "slack://lol@123456789/123456789/") }) }) Describe("the slack config", func() { When("parsing the configuration URL", func() { - It("should be identical after de-/serialization", func() { - testURL := "slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456?color=3f00fe&title=Test+title" + When("given a config using the legacy format", func() { + It("should be converted to the new format after de-/serialization", func() { + oldURL := "slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456?color=3f00fe&title=Test+title" + newURL := "slack://hook:AAAAAAAAA-BBBBBBBBB-123456789123456789123456@webhook?botname=testbot&color=3f00fe&title=Test+title" - url, err := url.Parse(testURL) - Expect(err).NotTo(HaveOccurred(), "parsing") + config := &Config{} + err := config.SetURL(urlMust(oldURL)) + Expect(err).NotTo(HaveOccurred(), "verifying") - config := &Config{} - err = config.SetURL(url) - Expect(err).NotTo(HaveOccurred(), "verifying") - - outputURL := config.GetURL() - Expect(outputURL.String()).To(Equal(testURL)) + Expect(config.GetURL().String()).To(Equal(newURL)) + }) }) }) + It("should be identical after de-/serialization", func() { + testURL := "slack://hook:AAAAAAAAA-BBBBBBBBB-123456789123456789123456@webhook?botname=testbot&color=3f00fe&title=Test+title" + + config := &Config{} + err := config.SetURL(urlMust(testURL)) + Expect(err).NotTo(HaveOccurred(), "verifying") + + outputURL := config.GetURL() + Expect(outputURL.String()).To(Equal(testURL)) + + }) When("generating a config object", func() { It("should use the default botname if the argument list contains three strings", func() { slackURL, _ := url.Parse("slack://AAAAAAAAA/BBBBBBBBB/123456789123456789123456") @@ -141,43 +124,112 @@ var _ = Describe("the slack service", func() { Expect(configError).To(HaveOccurred()) }) }) + When("getting credentials from token", func() { + It("should return a valid webhook URL for the given token", func() { + token := tokenMust("AAAAAAAAA/BBBBBBBBB/123456789123456789123456") + expected := "https://hooks.slack.com/services/AAAAAAAAA/BBBBBBBBB/123456789123456789123456" + Expect(token.WebhookURL()).To(Equal(expected)) + }) + It("should return a valid authorization header value for the given token", func() { + token := tokenMust("xoxb:AAAAAAAAA-BBBBBBBBB-123456789123456789123456") + expected := "Bearer xoxb-AAAAAAAAA-BBBBBBBBB-123456789123456789123456" + Expect(token.Authorization()).To(Equal(expected)) + }) + }) }) Describe("sending the payload", func() { - var err error - BeforeEach(func() { - httpmock.Activate() - }) - AfterEach(func() { - httpmock.DeactivateAndReset() - }) - It("should not report an error if the server accepts the payload", func() { - serviceURL, _ := url.Parse("slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456") - err = service.Initialize(serviceURL, logger) - Expect(err).NotTo(HaveOccurred()) + When("sending via webhook URL", func() { + var err error + BeforeEach(func() { + httpmock.Activate() + }) + AfterEach(func() { + httpmock.DeactivateAndReset() + }) - targetURL := "https://hooks.slack.com/services/AAAAAAAAA/BBBBBBBBB/123456789123456789123456" - httpmock.RegisterResponder("POST", targetURL, httpmock.NewStringResponder(200, "")) + It("should not report an error if the server accepts the payload", func() { + serviceURL, _ := url.Parse("slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456") + err = service.Initialize(serviceURL, logger) + Expect(err).NotTo(HaveOccurred()) - err = service.Send("Message", nil) - Expect(err).NotTo(HaveOccurred()) + targetURL := "https://hooks.slack.com/services/AAAAAAAAA/BBBBBBBBB/123456789123456789123456" + httpmock.RegisterResponder("POST", targetURL, httpmock.NewStringResponder(200, "")) + + err = service.Send("Message", nil) + Expect(err).NotTo(HaveOccurred()) + }) + It("should not panic if an error occurs when sending the payload", func() { + serviceURL, _ := url.Parse("slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456") + err = service.Initialize(serviceURL, logger) + Expect(err).NotTo(HaveOccurred()) + + targetURL := "https://hooks.slack.com/services/AAAAAAAAA/BBBBBBBBB/123456789123456789123456" + httpmock.RegisterResponder("POST", targetURL, httpmock.NewErrorResponder(errors.New("dummy error"))) + + err = service.Send("Message", nil) + Expect(err).To(HaveOccurred()) + }) }) - It("should not panic if an error occurs when sending the payload", func() { - serviceURL, _ := url.Parse("slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456") - err = service.Initialize(serviceURL, logger) - Expect(err).NotTo(HaveOccurred()) + When("sending via bot API", func() { + var err error + BeforeEach(func() { + httpmock.Activate() + }) + AfterEach(func() { + httpmock.DeactivateAndReset() + }) - targetURL := "https://hooks.slack.com/services/AAAAAAAAA/BBBBBBBBB/123456789123456789123456" - httpmock.RegisterResponder("POST", targetURL, httpmock.NewErrorResponder(errors.New("dummy error"))) + It("should not report an error if the server accepts the payload", func() { + serviceURL := urlMust("slack://xoxb:123456789012-1234567890123-4mt0t4l1YL3g1T5L4cK70k3N@C0123456789") + err = service.Initialize(serviceURL, logger) + Expect(err).NotTo(HaveOccurred()) - err = service.Send("Message", nil) - Expect(err).To(HaveOccurred()) + targetURL := "https://slack.com/api/chat.postMessage" + httpmock.RegisterResponder("POST", targetURL, jsonRespondMust(200, APIResponse{ + Ok: true, + })) + + err = service.Send("Message", nil) + Expect(err).NotTo(HaveOccurred()) + }) + It("should not panic if an error occurs when sending the payload", func() { + serviceURL := urlMust("slack://xoxb:123456789012-1234567890123-4mt0t4l1YL3g1T5L4cK70k3N@C0123456789") + err = service.Initialize(serviceURL, logger) + Expect(err).NotTo(HaveOccurred()) + + targetURL := "https://slack.com/api/chat.postMessage" + httpmock.RegisterResponder("POST", targetURL, jsonRespondMust(200, APIResponse{ + Error: "someone turned off the internet", + })) + + err = service.Send("Message", nil) + Expect(err).To(HaveOccurred()) + }) }) }) }) -func expectErrorMessageGivenURL(msg ErrorMessage, slackURL *url.URL) { - err := service.Initialize(slackURL, util.TestLogger()) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal(string(msg))) +func jsonRespondMust(code int, response APIResponse) httpmock.Responder { + responder, err := httpmock.NewJsonResponder(code, response) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "invalid test response struct") + return responder +} + +func urlMust(rawURL string) *url.URL { + parsed, err := url.Parse(rawURL) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "invalid test URL string") + return parsed +} + +func tokenMust(rawToken string) *Token { + token, err := ParseToken(rawToken) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + return token +} + +func expectErrorMessageGivenURL(expected error, rawURL string) { + err := service.Initialize(urlMust(rawURL), util.TestLogger()) + ExpectWithOffset(1, err).To(HaveOccurred()) + ExpectWithOffset(1, err).To(Equal(expected)) } diff --git a/pkg/services/slack/slack_token.go b/pkg/services/slack/slack_token.go index e6f4aff2..643d58f1 100644 --- a/pkg/services/slack/slack_token.go +++ b/pkg/services/slack/slack_token.go @@ -1,60 +1,121 @@ package slack import ( - "errors" + "fmt" + "github.com/containrrr/shoutrrr/pkg/types" + "net/url" "regexp" "strings" ) -// Token is a three part string split into A, B and C -type Token []string +var _ types.ConfigProp = &Token{} -// ValidateToken checks that the token is in the expected format -func ValidateToken(token Token) error { - if err := tokenPartsAreNotEmpty(token); err != nil { - return err - } else if err := tokenPartsAreValidFormat(token); err != nil { - return err - } - return nil +const ( + hookTokenIdentifier = "hook" + userTokenIdentifier = "xoxp" + botTokenIdentifier = "xoxb" +) + +// Token is a Slack API token or a Slack webhook token +type Token struct { + raw string } -func tokenPartsAreNotEmpty(token Token) error { - if token[0] == "" { - return errors.New(string(TokenAMissing)) - } else if token[1] == "" { - return errors.New(string(TokenBMissing)) - } else if token[2] == "" { - return errors.New(string(TokenCMissing)) +func (token *Token) SetFromProp(propValue string) error { + if len(propValue) < 3 { + return ErrorInvalidToken + } + + match := tokenPattern.FindStringSubmatch(propValue) + if match == nil || len(match) != tokenMatchCount { + return ErrorInvalidToken + } + + typeIdentifier := match[tokenMatchType] + if typeIdentifier == "" { + typeIdentifier = hookTokenIdentifier + } + + token.raw = fmt.Sprintf("%s:%s-%s-%s", + typeIdentifier, match[tokenMatchPart1], match[tokenMatchPart2], match[tokenMatchPart3]) + + if match[tokenMatchSep1] != match[tokenMatchSep2] { + return ErrorMismatchedTokenSeparators } + return nil } -func tokenPartsAreValidFormat(token Token) error { - if !matchesPattern("[A-Z0-9]{9}", token[0]) { - return errors.New(string(TokenAMalformed)) - } else if !matchesPattern("[A-Z0-9]{9}", token[1]) { - return errors.New(string(TokenBMalformed)) - } else if !matchesPattern("[A-Za-z0-9]{24}", token[2]) { - return errors.New(string(TokenCMalformed)) +func (token *Token) GetPropValue() (string, error) { + if token == nil { + return "", nil } - return nil + + return token.raw, nil } -func matchesPattern(pattern string, part string) bool { - matched, err := regexp.Match(pattern, []byte(part)) - if matched != true || err != nil { - return false +// TypeIdentifier returns the type identifier of the token +func (token Token) TypeIdentifier() string { + return token.raw[:4] +} + +// ParseToken parses and normalizes a token string +func ParseToken(str string) (*Token, error) { + token := &Token{} + if err := token.SetFromProp(str); err != nil { + return nil, err } - return true + return token, nil +} + +const ( + tokenMatchFull = iota + tokenMatchType + tokenMatchPart1 + tokenMatchSep1 + tokenMatchPart2 + tokenMatchSep2 + tokenMatchPart3 + tokenMatchCount +) + +var tokenPattern = regexp.MustCompile(`(?:(?P<type>xox.|hook)[-:]|:?)(?P<p1>[A-Z0-9]{9,})(?P<s1>[-/,])(?P<p2>[A-Z0-9]{9,})(?P<s2>[-/,])(?P<p3>[A-Za-z0-9]{24,})`) + +// String returns the token in normalized format with dashes (-) as separator +func (token *Token) String() string { + return token.raw } -func (t Token) String() string { - return strings.Join(t, "-") +func (token *Token) UserInfo() *url.Userinfo { + return url.UserPassword(token.raw[:4], token.raw[5:]) +} + +func (token *Token) IsAPIToken() bool { + return token.TypeIdentifier() != hookTokenIdentifier +} + +const webhookBase = "https://hooks.slack.com/services/" + +func (token Token) WebhookURL() string { + sb := strings.Builder{} + sb.WriteString(webhookBase) + sb.Grow(len(token.raw) - 5) + for i := 5; i < len(token.raw); i++ { + c := token.raw[i] + if c == '-' { + c = '/' + } + sb.WriteByte(c) + } + return sb.String() } -// ParseToken creates a Token from a sting representation -func ParseToken(s string) Token { - token := strings.Split(s, "-") - return token +func (token *Token) Authorization() string { + sb := strings.Builder{} + sb.WriteString("Bearer ") + sb.Grow(len(token.raw)) + sb.WriteString(token.raw[:4]) + sb.WriteRune('-') + sb.WriteString(token.raw[5:]) + return sb.String() } From a6547febfae1d15e049540d76073ca801947da61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= <nils@piksel.se> Date: Sat, 3 Jul 2021 22:57:49 +0200 Subject: [PATCH 2/6] test: fix common generator tests --- go.mod | 1 + go.sum | 34 ---------- pkg/util/generator/generator_common.go | 1 + pkg/util/generator/generator_test.go | 93 ++++++++++++++------------ 4 files changed, 53 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index a85a4782..71e60926 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/google/uuid v1.1.5 // indirect github.com/jarcoal/httpmock v1.0.4 github.com/klauspost/compress v1.11.7 // indirect + github.com/mattn/go-colorable v0.1.8 github.com/mitchellh/mapstructure v1.2.2 // indirect github.com/nxadm/tail v1.4.6 // indirect github.com/onsi/ginkgo v1.14.2 diff --git a/go.sum b/go.sum index e1969ea2..72f3b576 100644 --- a/go.sum +++ b/go.sum @@ -35,7 +35,6 @@ github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -69,9 +68,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= @@ -85,7 +82,6 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= @@ -93,7 +89,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190908185732-236ed259b199/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -107,7 +102,6 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -121,12 +115,10 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -137,7 +129,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -151,14 +142,12 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -168,13 +157,11 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.6 h1:11TGpSHY7Esh/i/qnq02Jo5oVrI1Gue8Slbq0ujPZFQ= github.com/nxadm/tail v1.4.6/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= @@ -183,7 +170,6 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= @@ -204,7 +190,6 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -213,33 +198,27 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs= github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -261,7 +240,6 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -274,7 +252,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= @@ -293,9 +270,7 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -312,7 +287,6 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -325,7 +299,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -344,14 +317,11 @@ google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyz google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -360,11 +330,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -374,7 +341,6 @@ gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8= -nhooyr.io/websocket v1.6.5 h1:8TzpkldRfefda5JST+CnOH135bzVPz5uzfn/AF+gVKg= nhooyr.io/websocket v1.6.5/go.mod h1:F259lAzPRAH0htX2y3ehpJe09ih1aSHN7udWki1defY= nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/pkg/util/generator/generator_common.go b/pkg/util/generator/generator_common.go index 9bc84434..38035a50 100644 --- a/pkg/util/generator/generator_common.go +++ b/pkg/util/generator/generator_common.go @@ -124,6 +124,7 @@ func (ud *UserDialog) QueryString(prompt string, validator func(string) error, k if err := validator(answer); err != nil { ud.Writeln("%v", err) + ud.Writeln("") continue } return answer diff --git a/pkg/util/generator/generator_test.go b/pkg/util/generator/generator_test.go index 645fb665..1388ec7a 100644 --- a/pkg/util/generator/generator_test.go +++ b/pkg/util/generator/generator_test.go @@ -3,6 +3,7 @@ package generator_test import ( "fmt" "github.com/containrrr/shoutrrr/pkg/util/generator" + "github.com/mattn/go-colorable" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" @@ -37,51 +38,51 @@ func dumpBuffers() { } var _ = Describe("GeneratorCommon", func() { - Describe("attach to the data stream", func() { - - BeforeEach(func() { - userOut = gbytes.NewBuffer() - userIn = gbytes.NewBuffer() - client = generator.NewUserDialog(userOut, userIn, map[string]string{"propKey": "propVal"}) - }) + BeforeEach(func() { + userOut = gbytes.NewBuffer() + userIn = gbytes.NewBuffer() + userInMono := colorable.NewNonColorable(userIn) + client = generator.NewUserDialog(userOut, userInMono, map[string]string{"propKey": "propVal"}) + }) - It("reprompt upon invalid answers", func() { - defer dumpBuffers() - answer := make(chan string) - go func() { - answer <- client.QueryString("name:", generator.Required, "") - }() + It("reprompt upon invalid answers", func() { + defer dumpBuffers() + answer := make(chan string) + go func() { + answer <- client.QueryString("name:", generator.Required, "") + }() - mockTyped("") - mockTyped("Normal Human Name") + mockTyped("") + mockTyped("Normal Human Name") - Eventually(userIn).Should(gbytes.Say(`name: `)) + Eventually(userIn).Should(gbytes.Say(`name: `)) - Eventually(userIn).Should(gbytes.Say(`field is required`)) - Eventually(userIn).Should(gbytes.Say(`name: `)) - Eventually(answer).Should(Receive(Equal("Normal Human Name"))) - }) + Eventually(userIn).Should(gbytes.Say(`field is required`)) + Eventually(userIn).Should(gbytes.Say(`name: `)) + Eventually(answer).Should(Receive(Equal("Normal Human Name"))) + }) - It("should accept any input when validator is nil", func() { - defer dumpBuffers() - answer := make(chan string) - go func() { - answer <- client.QueryString("name:", nil, "") - }() - mockTyped("") - Eventually(answer).Should(Receive(BeEmpty())) - }) + It("should accept any input when validator is nil", func() { + defer dumpBuffers() + answer := make(chan string) + go func() { + answer <- client.QueryString("name:", nil, "") + }() + mockTyped("") + Eventually(answer).Should(Receive(BeEmpty())) + }) - It("should use predefined prop value if key is present", func() { - defer dumpBuffers() - answer := make(chan string) - go func() { - answer <- client.QueryString("name:", generator.Required, "propKey") - }() - Eventually(answer).Should(Receive(Equal("propVal"))) - }) + It("should use predefined prop value if key is present", func() { + defer dumpBuffers() + answer := make(chan string) + go func() { + answer <- client.QueryString("name:", generator.Required, "propKey") + }() + Eventually(answer).Should(Receive(Equal("propVal"))) + }) - It("Query", func() { + Describe("Query", func() { + It("should prompt until a valid answer is provided", func() { defer dumpBuffers() answer := make(chan []string) query := "pick foo or bar:" @@ -97,8 +98,10 @@ var _ = Describe("GeneratorCommon", func() { Eventually(userIn).Should(gbytes.Say(query)) Eventually(answer).Should(Receive(ContainElement("foo"))) }) + }) - It("QueryAll", func() { + Describe("QueryAll", func() { + It("should prompt until a valid answer is provided", func() { defer dumpBuffers() answer := make(chan [][]string) query := "pick foo or bar:" @@ -114,8 +117,10 @@ var _ = Describe("GeneratorCommon", func() { Expect(matches).To(ContainElement([]string{"foobar", "bar"})) Expect(matches).To(ContainElement([]string{"foobaz", "baz"})) }) + }) - It("QueryStringPattern", func() { + Describe("QueryStringPattern", func() { + It("should prompt until a valid answer is provided", func() { defer dumpBuffers() answer := make(chan string) query := "type of bar:" @@ -131,8 +136,10 @@ var _ = Describe("GeneratorCommon", func() { Eventually(userIn).Should(gbytes.Say(query)) Eventually(answer).Should(Receive(Equal("foobar"))) }) + }) - It("QueryInt", func() { + Describe("QueryInt", func() { + It("should prompt until a valid answer is provided", func() { defer dumpBuffers() answer := make(chan int64) query := "number:" @@ -148,8 +155,10 @@ var _ = Describe("GeneratorCommon", func() { Eventually(userIn).Should(gbytes.Say(query)) Eventually(answer).Should(Receive(Equal(int64(32)))) }) + }) - It("QueryBool", func() { + Describe("QueryBool", func() { + It("should prompt until a valid answer is provided", func() { defer dumpBuffers() answer := make(chan bool) query := "cool?" From 002b5159cb50ca5390016cac268951f6bee624d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= <nils@piksel.se> Date: Sat, 3 Jul 2021 23:52:19 +0200 Subject: [PATCH 3/6] test(slack): add icon and invalid prop tests --- pkg/services/slack/slack_json.go | 27 +++++++++++++++++---------- pkg/services/slack/slack_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/pkg/services/slack/slack_json.go b/pkg/services/slack/slack_json.go index 903e5801..4d152068 100644 --- a/pkg/services/slack/slack_json.go +++ b/pkg/services/slack/slack_json.go @@ -5,8 +5,8 @@ import ( "strings" ) -// messagePayload used within the Slack service -type messagePayload struct { +// MessagePayload used within the Slack service +type MessagePayload struct { Text string `json:"text"` BotName string `json:"username,omitempty"` Blocks []block `json:"blocks,omitempty"` @@ -16,6 +16,19 @@ type messagePayload struct { IconURL string `json:"icon_url,omitempty"` } +func (p *MessagePayload) SetIcon(icon string) { + p.IconURL = "" + p.IconEmoji = "" + + if icon != "" { + if iconUrlPattern.MatchString(icon) { + p.IconURL = icon + } else { + p.IconEmoji = icon + } + } +} + type block struct { Type string `json:"type"` Text blockText `json:"text"` @@ -64,19 +77,13 @@ func CreateJSONPayload(config *Config, message string) interface{} { }) } - payload := messagePayload{ + payload := MessagePayload{ Text: config.Title, BotName: config.BotName, Attachments: atts, } - if config.Icon != "" { - if iconUrlPattern.MatchString(config.Icon) { - payload.IconURL = config.Icon - } else { - payload.IconEmoji = config.Icon - } - } + payload.SetIcon(config.Icon) if config.Channel != "webhook" { payload.Channel = config.Channel diff --git a/pkg/services/slack/slack_test.go b/pkg/services/slack/slack_test.go index c47190f4..aee8a158 100644 --- a/pkg/services/slack/slack_test.go +++ b/pkg/services/slack/slack_test.go @@ -91,6 +91,11 @@ var _ = Describe("the slack service", func() { }) }) }) + When("the URL contains an invalid property", func() { + testURL := urlMust("slack://hook:AAAAAAAAA-BBBBBBBBB-123456789123456789123456@webhook?bass=dirty") + err := (&Config{}).SetURL(testURL) + Expect(err).To(HaveOccurred()) + }) It("should be identical after de-/serialization", func() { testURL := "slack://hook:AAAAAAAAA-BBBBBBBBB-123456789123456789123456@webhook?botname=testbot&color=3f00fe&title=Test+title" @@ -138,6 +143,28 @@ var _ = Describe("the slack service", func() { }) }) + Describe("creating the payload", func() { + Describe("the icon fields", func() { + payload := MessagePayload{} + It("should set IconURL when the configured icon looks like an URL", func() { + payload.SetIcon("https://example.com/logo.png") + Expect(payload.IconURL).To(Equal("https://example.com/logo.png")) + Expect(payload.IconEmoji).To(BeEmpty()) + }) + It("should set IconEmoji when the configured icon does not look like an URL", func() { + payload.SetIcon("tanabata_tree") + Expect(payload.IconEmoji).To(Equal("tanabata_tree")) + Expect(payload.IconURL).To(BeEmpty()) + }) + It("should clear both fields when icon is empty", func() { + payload.SetIcon("") + Expect(payload.IconEmoji).To(BeEmpty()) + Expect(payload.IconURL).To(BeEmpty()) + }) + }) + + }) + Describe("sending the payload", func() { When("sending via webhook URL", func() { var err error From 362e4135793166640f0766f9f0fd91ce4f25b141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= <nils@piksel.se> Date: Sun, 18 Jul 2021 15:38:40 +0200 Subject: [PATCH 4/6] use generic JSON client header API --- pkg/services/slack/slack.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/services/slack/slack.go b/pkg/services/slack/slack.go index 24358402..84b69460 100644 --- a/pkg/services/slack/slack.go +++ b/pkg/services/slack/slack.go @@ -62,10 +62,8 @@ func (service *Service) Initialize(configURL *url.URL, logger types.StdLogger) e func (service *Service) sendApi(config *Config, payload interface{}) error { response := APIResponse{} - jsonClient := jsonclient.Client{ - HTTPClient: http.DefaultClient, - AuthorizationHeader: config.Token.Authorization(), - } + jsonClient := jsonclient.NewClient() + jsonClient.Headers().Set("Authorization", config.Token.Authorization()) if err := jsonClient.Post(apiPostMessage, payload, &response); err != nil { return err From 7e4c553601d2a14141bedf538ac6159a9a349c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= <nils@piksel.se> Date: Tue, 27 Jul 2021 11:19:57 +0200 Subject: [PATCH 5/6] lint fixes --- pkg/services/slack/slack.go | 11 +++++------ pkg/services/slack/slack_json.go | 8 +++++--- pkg/services/slack/slack_token.go | 8 ++++++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/pkg/services/slack/slack.go b/pkg/services/slack/slack.go index 84b69460..926cac3c 100644 --- a/pkg/services/slack/slack.go +++ b/pkg/services/slack/slack.go @@ -37,7 +37,7 @@ func (service *Service) Send(message string, params *types.Params) error { var err error if config.Token.IsAPIToken() { - err = service.sendApi(config, payload) + err = service.sendAPI(config, payload) } else { err = service.sendWebhook(config, payload) } @@ -54,13 +54,12 @@ func (service *Service) Initialize(configURL *url.URL, logger types.StdLogger) e service.Logger.SetLogger(logger) service.config = &Config{} service.pkr = format.NewPropKeyResolver(service.config) - if err := service.config.setURL(&service.pkr, configURL); err != nil { - return err - } - return nil + + return service.config.setURL(&service.pkr, configURL) + } -func (service *Service) sendApi(config *Config, payload interface{}) error { +func (service *Service) sendAPI(config *Config, payload interface{}) error { response := APIResponse{} jsonClient := jsonclient.NewClient() jsonClient.Headers().Set("Authorization", config.Token.Authorization()) diff --git a/pkg/services/slack/slack_json.go b/pkg/services/slack/slack_json.go index e6c4a79b..103a4280 100644 --- a/pkg/services/slack/slack_json.go +++ b/pkg/services/slack/slack_json.go @@ -17,12 +17,15 @@ type MessagePayload struct { IconURL string `json:"icon_url,omitempty"` } +var iconURLPattern = regexp.MustCompile(`https?://`) + +// SetIcon sets the appropriate icon field in the payload based on whether the input is a URL or not func (p *MessagePayload) SetIcon(icon string) { p.IconURL = "" p.IconEmoji = "" if icon != "" { - if iconUrlPattern.MatchString(icon) { + if iconURLPattern.MatchString(icon) { p.IconURL = icon } else { p.IconEmoji = icon @@ -56,6 +59,7 @@ type legacyField struct { Short bool `json:"short,omitempty"` } +// APIResponse is the default generic response message sent from the API type APIResponse struct { Ok bool `json:"ok"` Error string `json:"error"` @@ -65,8 +69,6 @@ type APIResponse struct { } `json:"response_metadata"` } -var iconUrlPattern = regexp.MustCompile(`https?://`) - // CreateJSONPayload compatible with the slack post message API func CreateJSONPayload(config *Config, message string) interface{} { diff --git a/pkg/services/slack/slack_token.go b/pkg/services/slack/slack_token.go index 643d58f1..2a506727 100644 --- a/pkg/services/slack/slack_token.go +++ b/pkg/services/slack/slack_token.go @@ -21,6 +21,8 @@ type Token struct { raw string } +// SetFromProp updates it's state according to the passed string +// (implementation of the types.ConfigProp interface) func (token *Token) SetFromProp(propValue string) error { if len(propValue) < 3 { return ErrorInvalidToken @@ -46,6 +48,8 @@ func (token *Token) SetFromProp(propValue string) error { return nil } +// GetPropValue returns a deserializable string representation of the token +// (implementation of the types.ConfigProp interface) func (token *Token) GetPropValue() (string, error) { if token == nil { return "", nil @@ -86,16 +90,19 @@ func (token *Token) String() string { return token.raw } +// UserInfo returns a url.Userinfo struct populated from the token func (token *Token) UserInfo() *url.Userinfo { return url.UserPassword(token.raw[:4], token.raw[5:]) } +// IsAPIToken returns whether the identifier is set to anything else but the webhook identifier (`hook`) func (token *Token) IsAPIToken() bool { return token.TypeIdentifier() != hookTokenIdentifier } const webhookBase = "https://hooks.slack.com/services/" +// WebhookURL returns the corresponding Webhook URL for the Token func (token Token) WebhookURL() string { sb := strings.Builder{} sb.WriteString(webhookBase) @@ -110,6 +117,7 @@ func (token Token) WebhookURL() string { return sb.String() } +// Authorization returns the corresponding `Authorization` HTTP header value for the Token func (token *Token) Authorization() string { sb := strings.Builder{} sb.WriteString("Bearer ") From cc5bb51540c723f46191e9179422d63e4d8f6cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= <nils@piksel.se> Date: Tue, 27 Jul 2021 14:41:00 +0200 Subject: [PATCH 6/6] update docs --- .../slack/app-api-channel-details-id.png | Bin 0 -> 5935 bytes .../guides/slack/app-api-copy-oauth-token.png | Bin 0 -> 22308 bytes docs/guides/slack/app-api-oauth-menu.png | Bin 0 -> 16321 bytes docs/guides/slack/app-api-select-channel.png | Bin 0 -> 19207 bytes docs/guides/slack/index.md | 47 ++++++++++++++++++ docs/index.md | 16 +++--- docs/services/slack.md | 31 ++++++------ docs/stylesheets/extra.css | 30 +++++++++++ mkdocs.yml | 3 ++ 9 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 docs/guides/slack/app-api-channel-details-id.png create mode 100644 docs/guides/slack/app-api-copy-oauth-token.png create mode 100644 docs/guides/slack/app-api-oauth-menu.png create mode 100644 docs/guides/slack/app-api-select-channel.png create mode 100644 docs/guides/slack/index.md create mode 100644 docs/stylesheets/extra.css diff --git a/docs/guides/slack/app-api-channel-details-id.png b/docs/guides/slack/app-api-channel-details-id.png new file mode 100644 index 0000000000000000000000000000000000000000..075b879f797f72bc29fbcb23b96e3a1405264c75 GIT binary patch literal 5935 zcmaKQc{r4B8}=ZEFqWxEV_(LWrTQ^;G8!6tVI=!H2!oP6TZ}YziLn!Y_Uy{OD`ZKG zq8Vh#WcS*AqxU_&<M{sh{&=3}-mdFDujjsx>%7k!Yp9Q4zRYzQ001!SXv2*G04flr zY)?-|dApYXNu_))Icw-?001@dj3>6Vlsbc#wz&@g0P4DUsIH3Me?e(v_tirAKJaw% z^|SSM1R%T}9X)(pJbkT9=qV=GYIWcm51(6+v)sx}w9@+ZcFU|4RWL2~w+4k=GyXgi z(bd*{N9Na8lGB(Vb2>xvak8=TM&Lp=Bboz%*1aAeiIM86M)q2+fGCT9Y;MVm0a^`d ztKL^Eth}O+d_sH8i@)8RQgHShS@Py_6F$cWKQpO#WKzG@ZuV8xd8451_|Jy~AaBTl zZ3(b*>Ob?LY9UUElq&zP`44`cMBRnKc(pUmXY12!1OlOp($5=mv|4<3h^q7*i|8xQ zbwYia*Sq&Y$K*z0LA+}0GZR7U;60P+W*=vwg-_g4;x#BP{7yuz#<kgoClWKzA3uPT zG>IliNKWWkGZ-O{9A*WYy(Z5#Ml=7Gj~vdFkN)w&kHh%x0qK=A$%Gx~ztQ?1yX>i{ zsTDo}$U@`|Id^)C26qNBne2$QKR-JqCL5?SY_uJFlzW;TSEBUsuNBqK4i+|--dOkf zVm3Y74n=}CJAq?#?s+_zAopD6I@-V*;U`FOU8ik3;9-r<uC3D1{`w3UdNS+T&i&-% z=y#R9;!~gAsYDubm+Fv99+O;wU{u|3hF6{A@2Lt|UODX_rG`8zyEl@0pFLsO8+yK` z*jVR|Rh(F%&(lBB_XW5oJo~Z{XIqN-Vvl`-&OwKzSHF#kt;yFr-Y36O9Mj{@2{}Sz z))TysYK+`9t&fXC_XG(BAp}j{d|)2a)07OFxXj&5z--KV!bh^Be$f7mbMHQSzMO&7 z!KsLQiTMoW=40-|xJc6U_vF@AM{Lk9ufXZT@3bXPy>AvK;4kZITMfxz1`W8Gaz#LN z+DbGn)r`&>z^8c;%G=!0w7RTAvM}F|Od;Yc;rK#JrAK}}op(#(-`8D*YVk&3ZztxQ zby5}Pu&(u%j-_6fv&sAeCYZ;<9H@~N2ZbK)I#IhRu%Q{;N{g0n)~;B9OZ-axC1|4n zh+lDcqJKAaS}GV3T=;;c0X;q$9&!?~)e}M=sR*-%yaFYGz5gxbJ-?DvpnQ1Hqe_ro z`l5M*b%=Ds_+`u6&%5A|I2B{u$7#V3zY=oMe6Q3|vMk)yyYz1N%0s#vSkRPg9!xqv z3y1rvA7<mGr~@BBgj{D8%DS_%thB7PqDvng#%`<5>9VHtly6a3++~BWPcnbjv0fu5 zIV35VcC9Nh((pme?&?w9X}(hH{Rxm8ujY0EOU>H-(bbm3Iu!w!($X%<#rE-wn14X4 z3fsk}aEpnptNd!g5_fNK`t?~y`qmWSLkK#B4toU&N^nCR7slkb*x{ON!SOeiU#%if z;F6POw|hD}#$^;=SI5X8@iI?k;5o1Q8ER}KS`KuhMuhZ~?AB04_($&SV@t-|0!-96 zJ7(79bk1D~HV`>2phB*2nNznp_JLYI;cY$Y$XBVNVWrxPFIf{wP&2h$ZLW()?&TCA z8TVZ>B~^~_)2{V;vjxHx-S2g_9me9h;SO;e_M%U>2czh$)y)V_QqK+okVxcYG;^Jv z0QL1i)v@72$^F8l298cAsiuTSdaQ^zfMS<8(^5Ll<+QqfC9*Vv{|KLot0=<11|@(v zbh2)K9fZ#Jim+KqedWLVLDC5U)xVoVVoiZwN&O2q_Gw7HyrNCZa$fIv@hrNY)_4J> zc?AEzc#B5r&Lu4$8ABU)SA6WsB2T$YXA|l`pF{?eP1O3m$M^!qjOhB|ehG{m5BnZU zmB;<lGOis*Z%H5KT_|WdW)<1itv~ej5(W;u)g_7Pn(Sl^#(|RPu#_E8El@2BFRJ>S za<%_%x#H@;N95HZ&2Uqc>rBb-`tb@M*cHcV&oJftSQFE4#bnAR!8Aw;9F9e!o)#^S zt6t;&QU5UUG)yvS%C_EMd9XmO;Y0U%eX9Vzz@S_XBY8^A8M@AYq$?b+665iTzD=F` zpW26=GYqvh=5gU@`6q;jz87dd5yhbVO6UX{x#GLmMQ(orZX3VUCp2N~F*fCjW3)Op z(lmS#sUA3Q{oV1I)vxeKpF&yL5(K^BT2b4iLWNHPM_vWYNUUb)(Wz+g3zheIB4m3X z-3?|eCCTh8kFvb6d}a<KMuNM>5WL(t#Whg`GcnyB#{kWKZnrYu9{PN>=*@jAmvolT zynPupOUWBa?IJNW-8P#0ay>@;VdXtx16k?MGyjdaa)7Itc4tUVd(G3OI2Dc|Pv=!Z zFnlYi7LRmIEBBKSB0OfgzOf!NGYyepeB}<nOdXw5N%=2W@+!P>B$30SrU;L6{KN(n zaJXmT1tFyEGjuiYjH=E1nj*-gaisLkI2-3M*rw*&HwU6^0&K`(cbr@}3S6ZW8Ne^v zy1V9~un>1!x8HYCmGi^H@}gJ@HAtlhYC+&Pm*liq8msTrK*o*KY?$g{7~cX21IdOr zs>r;wevO!RRl+nKw^E`elO~`Kk%~4^Cb&QfjSvbLl2_^d>AXh#7o$)c$=X<OG}5Gt ztn!J0dk@%ms|<!|dMKPmqqxRl-iz%n60gCJ8nq9IQ(Tx}mx6FdTBd&;NG|(KO1~fC zXX{Tq%1cC&DShT+AN)3CJia%{*!RWBU`Ra&JI)f;es&b5u<Z<|%`>aAatiPx#Y$At zyG@ldu&C@B7Vq@)1iG(R`S1LS;!z3ky-#E8F<os7EHb%kI;aOZsS4v&Y;NL9+cBZW zso8c#9?;X?e4Jnva=7Gmvelot)IxJ2O~b23uABJ0`={q-<9GJs7}c?51C_l=lA3Ka zh?^kBtlf;@4c^VIyFVb_jEzuhO*x)OL;6tgrh0v|^y7X$2M-KvT78!(kcbc7pRrvX zFCy;Gddh$D<yZ@Ybx=5wM<=2UIpLZFJIH)K<$R~<P8({(40JLOgAn;S4QomT;sgIQ z+a7K&ZBcza3G@7%E{jR3j<gZeC{gwM^F4h;?O%q`jiv=$wa1T^bhF*>&t{(xU(3(Y z(*Qy16v_x)n=IYBOAq@6>FHd!_UrT82SHvV_1~-l4X>{ig_w(1_xMxTtQ%~gj4@P5 zW0EkdWGX<!<Cb-Dl>NOi<G&lm<s*PBxMj*F+smu<oNwDY-XwMx{8+K7cQqliMW390 zKXR)_?oc9L`ch@xeo!cmKiQoykl(@k&G~PUdO|~<J6%d<2&F`^Y$wEb)?YxMcGKb- zlwtW^9H)^`#shtYB5h;cUDe>DD|<dX;lxl?w{MlquQ;S}qYs<c{w)*0R6E#7vxIli ziJc!T#P^5t-L@6$PvXw}VqOUWa}qWB3u`{}k-}G`+73uAx4|KVI<)QS(H;h88G5?s zh>bY<;@3&b0Uz&>@>LgILAb`k09l_ycPt-EE=vF*+|kmF5F2U@riK3a_<4S+ffv|; z$b88{elz#VsRQA><Fcx@NB9)YmrU4Cu5Z6n(}!dAgpyt!v18~<Z$564S!?>!@>_99 zFe_-W>LHGSiHh|!p$|HbH3amt2V44%X>GB*Vs$bl<({$v$-;U=Um=)|m};)R<~xlS z3+<fs)b!+=FdHp@OKLPW9|}&ucJa{n@Pn`EX510G$70`)WB&HoM^A`WfZBYBbxGTb z59~%+#;g&VU036YhHCWjfM7g)+|K(!@w8DTo;A7-u?A(~GwyUEKwqr;st%#GL)9Ti zsNYozBtT@QGEMl158A2*U&S7CIbBlsad5BmIki`P@7B^~JsuGW2cMl`d0sf=@efD4 zq&M2(Whw^J1yX}hzFVEqVsGB13IGJszgdT>=zHxeOy4FR*4iOTy{8}IV1*S)@A7tv z&>51jcNLO>zRejzpbmBR>_hU*udAO9#lw`Sw>cHFcN86mQZ~C7rF*a)zxa_?L{uF# ztSYx7NxI)}k1F5RHyqLgys&SL?-X)w&hPeP7LiwMI(Q9HxFrQOt+L|&Ev^Mv_w>qs zJ)Nx*_|mB0o)&v)8VnV%+Az*T&{<4Lg=Gh<l{VE`)VUbdFz-GYW(Vf(tjchX79I9@ zw@LmyEL84i2O<uH!c&#lPFAqOHHBNBAB4!fgQ|c*MAZ2-12sRFjeXtZ{8=-jCeNkQ z>2SRz`WL74Qb~);&9%xt4Ub4a0|}-z!V;J-R)qWJ>-IZobr~Wc^@150$Dig8Zsp3T zGGoy0HF#+j)T8RWbPwaSaMoyD6yu5hrS_EJua%b?tqoKU9d1{!C785C!Y;AWf8yx6 zNB73!0j}@mQcyHFHKt(lCJG^kvUkrzV!OwJs_`3M;RB0!pAoUMO)*+O)dTJr$?K3q zo7Xk8P!PhT`XY%|_f3a`qYK1QBg-Y$6w^*@x6#$Vd9+-%;b#xA<nmcHsgKGY{Xe6h zOXF!^#Wj|=qJR3X;b9OHViHnHHtTQNuK1q?GEMUTZQ<g1I;pk|ztKT~B`msU$b%q9 z)YSIXK`0I^>Ee~wSo{T<!zeJ2or>3;9irzvzfSRRg8N>|4%HSXc451ThCiD*p;^93 z3afXXqu!genNy&736;QkhV{89UWqb>vC8;6po^ktSPG?hb{PtaE?|nJ6cxKEttEhg zZpHb}UL-FVq?t%DhIwB!do8{MM|Tqkm?@*X6L084KnU8+wp<ELMatUu^_Z!-DsOhs zY^eyEHs8BPQ8=Dq3#WJ*IwpD!tLL^s5(*K$xmztNbJ9a@A{Fm{Ft|5U6iip8<p<FH z1q02G&sdan`fFTX*&J*VZRbAvZ|N<gXN>uz9YtT^G$QDt6nuVN`R(cH$#Su>P1{2f z_Sq(7roDdelKP+<@YcO43;Ux;2C!X71+RtkXtrgYlAb*iC*zuvJEx#kmUfFrm6Ta! z{PR>nX`Bel{9t$HH(1Uut@aZ(2{%<j-0--y-+Zv23mT0@Ph(q?<Rz+8{XSP(G)fWp zbEW;uPFnvncVph#E%nL6$x9gb*@l~OoU#HR_X%!^qMSi<Q~yvH^T2g5Km;@g6Iq3E z;O-szQd_*Z!xOk(BNKeIn{U58$e1zjznp~{E!0WwBK5wq&)M3)g-5L|CU4-Ol+^Yn zWHDK_?P#rxMF7x8eCwQzl@6MtC`ws1&roN(e1k@_p)lj%o?c>h;2&(VIT(1_12?Gp zp27P5R|Kj=lkhq7_eT5q*@3I^y7CoLE-$~*y1~Ske<&ihL`~1l0fHgfcyHniFd-5K z1*|~6*Yw0xGpkkKIcop(rXqnWRsl@1n*=?Des~y!k29Y}q`7d*+sVr7GfPJG!fJ62 zXyTn7BZW3pVgPeNJHyYTIL51UXUj${g5fuMc)E^LU@;&*ogb#VpM*v;;u}^}qjO8+ zb>g%9V<KL0jPV%Z!3bvxhUvE8Ku(o_It5W`eB59Moa>f@c4Xuttb4o&xuDR6sT?6a zWD>W9^$6LPJ+AmNI2z+ToN@INI;$`_Uc!u`eeD(2Q@p>j0&Tos)z&vOyo{#E_T2rQ z2FwUJYITQe5)t?h(T?}or{d|G=?XqhC`y)6PAij<bEQ`24Okw@nk{v|{KG0(l`Ob> z6IIQpe15XC1wk+J>$SC0<TpkPo}yW&$#tZW9mG{QVH(4AL&KCbO}P{hQj|g^B>p#u z4z$EmW&OSmS)x}LMZNbAe*T&uF|@t(NznW!=YwMXoBDEesbx+U78a87p=T%0Q<t6` zrTVhZy?-bCgd(Rgp)Vu?6~*q9NHbz&-p;rP;+@{!Q&oM+Gi0x16s1~4C3g4H5nfk@ z@N_xL?;5lOaAV?@7-ry-1mcl;qgjr@am*4;a)K=TagWq#mLqGSdVBKAkOXMXiy-;$ zUYeNFJ_WQ{#<%drDCJdNC~l<W2I&hjb@K2Ir?yB6#0@$IF2w6YEDh*m=(=p#ll4`% zUs+K-)lnF)WkqC&<OZp*0u%AH1ltq2xXRNQWlY7H_p8ijgU9Z!|Gw)H8)AR;D0 z&Wr7*9UuAq_Pd0StU!BpOSCn$02dS|2&nu8GcPK-U#g=I?|?fiba4amrXu&&49&3- zw^reRl#9*I(Mmmp4H-wWMO*z_h^F2bL}l`h6vgrhgJO~fDkRdxr#<(Cb3gv^8+v~o zWXpb9lc>ux;ynB&T$opmazK?O3}Q+@awWY{eNiG}knP?;m1EK5WxIYGX;6?aC6A~F zV?HEO{kq1#go$;<(m?GezIfA(I~o_FCxwr68e<~r9!*11eJIm?`I=ck{bO{>PyGWb z)~gZGCyi8Hf1z#u>^=|rw!col6&YN_=P!}yTuX`p13NI3BPmyv{(E9nxA#~39ad*C z4(@6rIy5WpmifQJAQ6EZ$*lANHP{5yP-6T^zL~mZeh%g4pnSHbNDe{e&qpT`@g1in zA4$#cg`dL-4<*bi8u|q&rL}UQYdZu$xK(7ZlIh)04<MsX@sfJ8>iDU9FmT0*H{vz= zdVBfdfx~qgTkXtdnXynxmU5rWH%K6`IQS3ssbm&-w(gm-9Tc@y{D>=IAOUnC_!K1w zjQY3FOjD2feLZiFw``ThIMFcrNG0LLZfsAUx+&?W5*w47k2^nP2?sC>86QN1j7j8H zroenrpA;2NsZgcEMpXW#7HPKfbI<L^u8s4*(LAN%Jk2B11e`}a^Xk9cT4+m?tmC_~ zfB(8+W&C)Eemf(dm=yi35c3^d#>}77;!m!T1epUT<lK12a^uaX@~k673%`%ITsOQ; z;gv}mr;(#}uc@37mgT+QyjFNK|K^#J3*PYtjHBK=IQDy!m$Z3Ivz=a0m?x1Y)aQFu z*V*ZDcU$m$^g_tt?`mfQpFch@3=E>MS(}r?35PXo-=U!}B@W_6f&nN*B?vSoH7$-) z5w|7iqEaQSS<1}X*`~@>Hg6x?i~j3W45OQ~^*DWdvRs>|^?YxA^LQ@wa>2RZW1)n5 zB5eP2!AeY@AFgX?h?+m^ZiiD-75w@5ZSuj!U#PiPFeg9e&mRz1Iu2U{+d)sB<V5nc zA7+&n&s~t`qWU$GwocFZ|3<4P)3z_Lu09>Q9wm4|q+?X4g3@{rNiF!ZjkyP~%eKUP z$G<%Vt1`|bJH*lRvw7L~oN_@Ag;@0<<~<Z5cDbd_1!sYSzs-AD92{cWy-A|~CG$4t zeMaP)3MoVNm{5=3Grw7)JZJuEpsaHG_RB(J@yjfwS;jl8irmf@krgn=ZtvIR8O)!@ zp1&NHNI9IfJ|j$8{0&R|XG^DibXe$h)7*%4fp&p<-zlYwAIo%*Q$`ovpObEt8|mJ4 zjyeG2i>m$x>Hs(oI_=$pM(brhx?E>yenyTzXS1?w8J{%!>+zP|$pLmN-tDO0s%UYn z|6L^2w>gJ3^rgSc*TK?xR<n$@{n4^V8@nQ@Z0fbh{hyxsppVUexE~+{FX9`(Vw2zX zIWnqUwrF<Mo37q!J-88n+^-iiy2uH0K^>Hk?Q7i&E=IyGZFWIpNb$jEw@-F9j{1so zSXTMi_wSaUyr#SO=YJ0`(Sq`nct8k`i}`Wi|No~Q@O;!cFmD8EA~9EapK`ek(9zO| JSKhS=|34k5W*Pth literal 0 HcmV?d00001 diff --git a/docs/guides/slack/app-api-copy-oauth-token.png b/docs/guides/slack/app-api-copy-oauth-token.png new file mode 100644 index 0000000000000000000000000000000000000000..135d01104f2ff30fd88c7f11a850a92d12871f25 GIT binary patch literal 22308 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 YfJD*tydKAo?q>nq)-}?p(0&yDKh!W)3IG5A literal 0 HcmV?d00001 diff --git a/docs/guides/slack/app-api-oauth-menu.png b/docs/guides/slack/app-api-oauth-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..9df1f9bfa69ef0283e8b0cc212bd306f8f5a88b1 GIT binary patch literal 16321 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 literal 0 HcmV?d00001 diff --git a/docs/guides/slack/app-api-select-channel.png b/docs/guides/slack/app-api-select-channel.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f3c4257f7007ba09a439e2869da6c613018b6e GIT binary patch literal 19207 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 B+tdI6 literal 0 HcmV?d00001 diff --git a/docs/guides/slack/index.md b/docs/guides/slack/index.md new file mode 100644 index 00000000..bba50231 --- /dev/null +++ b/docs/guides/slack/index.md @@ -0,0 +1,47 @@ +# Slack Guides + +Guides for setting up the [Slack](../../services/slack.md) service + +## Getting a token + +To enable all features, either the Legacy Webhook- (deprecated and might stop working) or the bot API tokens needs to +be used. Only use the non-legacy Webhook if you don't need to customize the bot name or icon. + +### Bot API (preferred) + +1. Create a new App for your bot using the [Basic app setup guide](https://api.slack.com/authentication/basics) +2. Install the App into your workspace ([slack docs](https://api.slack.com/authentication/basics#installing)). +3. From [Apps](https://api.slack.com/apps), select your new App and go to **Oauth & Permissions** + <figure><img alt="Slack app management menu screenshot" src="app-api-oauth-menu.png" height="248" /></figure> +4. Copy the Bot User OAuth Token + <figure><img alt="Copy OAuth token screenshot" src="app-api-copy-oauth-token.png" height="209" /></figure> + +!!! example + Given the API token + <pre><code><b>xoxb</b>-<b>123456789012</b>-<b>1234567890123</b>-<b>4mt0t4l1YL3g1T5L4cK70k3N</b></code></pre> + and the channel ID `C001CH4NN3L` (obtained by using the [guide below](#getting_the_channel_id)), the Shoutrrr URL + should look like this: + <pre><code>slack://<b>xoxb</b>:<b>123456789012</b>-<b>1234567890123</b>-<b>4mt0t4l1YL3g1T5L4cK70k3N</b>@<b>C001CH4NN3L</b></code></pre> + +### Webhook tokens + +Get a Webhook URL using the legacy [WebHooks Integration](https://slack.com/apps/new/A0F7XDUAZ-incoming-webhooks), +or by using the [Getting started with Incoming Webhooks](https://api.slack.com/messaging/webhooks#getting_started) guide and +replace the initial `https://hooks.slack.com/services/` part of the webhook URL with `slack://hook:` to get your Shoutrrr URL. + +!!! info "Slack Webhook URL" + <code>https://hooks.slack.com/services/<b>T00000000</b>/<b>B00000000</b>/<b>XXXXXXXXXXXXXXXXXXXXXXXX</b></code> + +!!! info "Shoutrrr URL" + <code>slack://hook:<b>T00000000</b>-<b>B00000000</b>-<b>XXXXXXXXXXXXXXXXXXXXXXXX</b>@webhook</code> + +## Getting the Channel ID + +!!! note "" + Only needed for API token. Use `webhook` as the channel for webhook tokens. + +1. In the channel you wish to post to, open **Channel Details** by clicking on the channel title. + <figure><img alt="Opening channel details screenshot" src="app-api-select-channel.png" height="270" /></figure> + +2. Copy the Channel ID from the bottom of the popup and append it to your Shoutrrr URL + <figure><img alt="Copy channel ID screenshot" src="app-api-channel-details-id.png" height="99" /></figure> \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 7c5741a6..2a472e53 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,27 +9,27 @@ Notification library for gophers and their furry friends.<br /> Heavily inspired by <a href="https://github.com/caronc/apprise">caronc/apprise</a>. </p> -<p align="center"> +<p align="center" class="badges"> <a target="_blank" rel="noopener noreferrer" href="https://github.com/containrrr/shoutrrr/workflows/Main%20Workflow/badge.svg"> - <img src="https://github.com/containrrr/shoutrrr/workflows/Main%20Workflow/badge.svg" alt="github actions workflow status" style="max-width:100%;"> + <img src="https://github.com/containrrr/shoutrrr/workflows/Main%20Workflow/badge.svg" alt="github actions workflow status"> </a> <a href="https://codecov.io/gh/containrrr/shoutrrr" rel="nofollow"> - <img alt="codecov" src="https://codecov.io/gh/containrrr/shoutrrr/branch/master/graph/badge.svg" style="max-width:100%;"> + <img alt="codecov" src="https://codecov.io/gh/containrrr/shoutrrr/branch/master/graph/badge.svg"> </a> <a href="https://www.codacy.com/gh/containrrr/shoutrrr/dashboard?utm_source=github.com&utm_medium=referral&utm_content=containrrr/shoutrrr&utm_campaign=Badge_Grade" rel="nofollow"> - <img alt="Codacy Badge" src="https://app.codacy.com/project/badge/Grade/47eed72de79448e2a6e297d770355544" style="max-width:100%;"> + <img alt="Codacy Badge" src="https://app.codacy.com/project/badge/Grade/47eed72de79448e2a6e297d770355544"> </a> <a href="https://goreportcard.com/badge/github.com/containrrr/shoutrrr" rel="nofollow"> - <img alt="report card" src="https://goreportcard.com/badge/github.com/containrrr/shoutrrr" style="max-width:100%;"> + <img alt="report card" src="https://goreportcard.com/badge/github.com/containrrr/shoutrrr"> </a> <a href="https://pkg.go.dev/github.com/containrrr/shoutrrr" rel="nofollow"> - <img alt="go.dev reference" src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square" style="max-width:100%;"> + <img alt="go.dev reference" src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square"> </a> <a href="https://github.com/containrrr/shoutrrr"> - <img alt="github code size in bytes" src="https://img.shields.io/github/languages/code-size/containrrr/shoutrrr.svg?style=flat-square" style="max-width:100%;"> + <img alt="github code size in bytes" src="https://img.shields.io/github/languages/code-size/containrrr/shoutrrr.svg?style=flat-square"> </a> <a href="https://github.com/containrrr/shoutrrr/blob/master/LICENSE"> - <img alt="license" src="https://img.shields.io/github/license/containrrr/shoutrrr.svg?style=flat-square" style="max-width:100%;"> + <img alt="license" src="https://img.shields.io/github/license/containrrr/shoutrrr.svg?style=flat-square"> </a> </p> diff --git a/docs/services/slack.md b/docs/services/slack.md index c011f34c..9cb7e230 100644 --- a/docs/services/slack.md +++ b/docs/services/slack.md @@ -1,22 +1,21 @@ # Slack -The slack notification service uses [Slack Webhook](https://api.slack.com/messaging/webhooks)s to send messages. -Follow the [Getting started with Incoming Webhooks](https://api.slack.com/messaging/webhooks#getting_started) guide and -replace the initial `https://hooks.slack.com/services/` part of the webhook URL with `slack://` to get your Shoutrrr URL. +!!! attention "New URL format" + The URL format for Slack has been changed to allow for API- as well as webhook tokens. + Using the old format (`slack://xxxx/yyyy/zzzz`) will still work as before and will automatically be upgraded to + the new format when used. -*Slack Webhook URL:* +The Slack notification service uses either [Slack Webhooks](https://api.slack.com/messaging/webhooks) or the +[Bot API](https://api.slack.com/methods/chat.postMessage) to send messages. -!!! info "" - https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX +See the [guides](../guides/slack/index.md) for information on how to get your *token* and *channel*. -Shoutrrr URL: -!!! info "" - slack://T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX +## URL Format +!!! note "" + Note that the token uses a prefix to determine the type, usually either `hook` (for webhooks) or `xoxb` (for bot API). -## URL Format - --8<-- "docs/services/slack/config.md" !!! info "Color format" @@ -26,8 +25,12 @@ Shoutrrr URL: ## Examples -!!! example - All fields set: +!!! example "Bot API" + ```uri + slack://xoxb:123456789012-1234567890123-4mt0t4l1YL3g1T5L4cK70k3N@C001CH4NN3L?color=good&title=Great+News&icon=man-scientist&botname=Shoutrrrbot + ``` + +!!! example "Webhook" ```uri - slack://ShoutrrrBot@T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX?color=good&title=Great+News + slack://hook:WNA3PBYV6-F20DUQND3RQ-Webc4MAvoacrpPakR8phF0zi@webhook?color=good&title=Great+News&icon=man-scientist&botname=Shoutrrrbot ``` \ No newline at end of file diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 00000000..83c0653f --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,30 @@ + +.md-typeset li img { + display: inline-block; +} + +.md-typeset figure { + background: var(--md-code-bg-color); + display: block; + width: 100%; +} + +.md-typeset figure img { + box-shadow: 2px 2px 4px #00000080; + padding: 3px; + background: var(--md-code-bg-color); +} + + +.md-typeset li img:last-child { + margin: 10px 0; +} + +.badges img { + height: 20px; + max-width: 100%; + display: inline-block; + padding: 0; + background: transparent; + border-radius: 3px; +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 78e6021b..c81d9f19 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,6 +15,7 @@ extra: generator: false extra_css: - stylesheets/theme.css + - stylesheets/extra.css markdown_extensions: - toc: permalink: True @@ -49,6 +50,8 @@ nav: - Telegram: 'services/telegram.md' - Zulip Chat: 'services/zulip.md' - Generic Webhook: 'services/generic.md' + - Guides: + - Slack: 'guides/slack/index.md' - Advanced usage: - Proxy: 'proxy.md'