diff --git a/webhook.go b/webhook.go index 5e5d64d..e32b718 100644 --- a/webhook.go +++ b/webhook.go @@ -1,12 +1,15 @@ package webhook import ( + "bytes" "errors" + "regexp" "github.com/requilence/integram" ) var m = integram.HTMLRichText{} +var markdownRichText = integram.MarkdownRichText{} type Config struct{ integram.BotConfig @@ -17,16 +20,17 @@ type webhook struct { Mrkdwn bool Channel string Attachments []struct { - Pretext string `json:"pretext"` - Fallback string `json:"fallback"` - AuthorName string `json:"author_name"` - AuthorLink string `json:"author_link"` - Title string `json:"title"` - TitleLink string `json:"title_link"` - Text string `json:"text"` - ImageURL string `json:"image_url"` - ThumbURL string `json:"thumb_url"` - Ts int `json:"ts"` + Pretext string `json:"pretext"` + Fallback string `json:"fallback"` + AuthorName string `json:"author_name"` + AuthorLink string `json:"author_link"` + Title string `json:"title"` + TitleLink string `json:"title_link"` + Text string `json:"text"` + MrkdwnIn []string `json:"mrkdwn_in"` + ImageURL string `json:"image_url"` + ThumbURL string `json:"thumb_url"` + Ts int `json:"ts"` } `json:"attachments"` } @@ -60,6 +64,62 @@ func update(c *integram.Context) error { return nil } +func convertLinks(text string, regex *regexp.Regexp, encodeEntities func(string) string, linkFormat func(string, string) string) string { + if encodeEntities == nil { + encodeEntities = func(text string) string { + return text + } + } + submatches := regex.FindAllStringSubmatchIndex(text, -1) + if submatches == nil { + return encodeEntities(text) + } + + convertedBuffer := new(bytes.Buffer) + convertedBuffer.Grow(len(text)) + currentPosition := 0 + for _, submatch := range submatches { + if submatch[0] > 0 { + convertedBuffer.WriteString(encodeEntities(text[currentPosition:submatch[0]])) + } + if submatch[2] < 0 { + // Code block, copy as-is + convertedBuffer.WriteString(encodeEntities(text[submatch[0]:submatch[1]])) + } else { + // URL link, convert + url := text[submatch[2]:submatch[3]] + displayText := url + if submatch[4] > 0 && submatch[4] != submatch[5] { + displayText = text[submatch[4] + 1:submatch[5]] + } + convertedBuffer.WriteString(linkFormat(displayText, url)) + } + currentPosition = submatch[1] + } + if currentPosition < len(text) { + convertedBuffer.WriteString(encodeEntities(text[currentPosition:])) + } + return convertedBuffer.String() +} + +func convertLinksToMarkdown(text string) string { + // Escape URL links if outside code blocks. + // Message format is documented at https://api.slack.com/docs/message-formatting) + // References to a Slack channel (@), user (#) or variable (!) are kept as-is + linkOrCodeBlockRegexp := regexp.MustCompile("```.+```|`[^`\n]+`|<([^@#! \n][^|> \n]*)(|[^>\n]+)?>") + return convertLinks(text, linkOrCodeBlockRegexp, nil, markdownRichText.URL); +} + +func convertLinksToHtml(text string) string { + linkOrCodeBlockRegexp := regexp.MustCompile(".*|
.*
|<([^@#! \n][^|> \n]*)(|[^>\n]+)?>") + return convertLinks(text, linkOrCodeBlockRegexp, nil, m.URL); +} + +func convertPlainWithLinksToHTML(text string) string { + linkRegexp := regexp.MustCompile("<([^@#! \n][^|> \n]*)(|[^>\n]+)?>") + return convertLinks(text, linkRegexp, m.EncodeEntities, m.URL); +} + func webhookHandler(c *integram.Context, wc *integram.WebhookContext) (err error) { wh := webhook{Mrkdwn: true} @@ -81,6 +141,7 @@ func webhookHandler(c *integram.Context, wc *integram.WebhookContext) (err error } haveAttachmentWithText:=false + haveMrkdwnAttachment:=false for i, attachment := range wh.Attachments { if i > 0 { text += "\n" @@ -99,19 +160,32 @@ func webhookHandler(c *integram.Context, wc *integram.WebhookContext) (err error } haveAttachmentWithText = true + for _, field := range attachment.MrkdwnIn { + if field == "pretext" { + haveMrkdwnAttachment = true + } + } text += attachment.Pretext } if haveAttachmentWithText { - return c.NewMessage().SetText(text).EnableAntiFlood().EnableHTML().Send() + m := c.NewMessage().EnableAntiFlood() + if haveMrkdwnAttachment { + m.SetText(convertLinksToMarkdown(text)).EnableMarkdown() + } else { + m.SetText(convertLinksToHtml(text)).EnableHTML() + } + return m.Send() } } if wh.Text != "" { - m := c.NewMessage().SetText(wh.Text + " " + wh.Channel).EnableAntiFlood() + m := c.NewMessage().EnableAntiFlood() if wh.Mrkdwn { - m.EnableMarkdown() + m.SetText(convertLinksToMarkdown(wh.Text + " " + wh.Channel)).EnableMarkdown() + } else { + m.SetText(convertPlainWithLinksToHTML(wh.Text + " " + wh.Channel)).EnableHTML() } return m.Send() }