Skip to content

Commit fcf40bf

Browse files
committed
Support Slack links in HTML/Markdown text
1 parent fcb2b48 commit fcf40bf

File tree

1 file changed

+60
-35
lines changed

1 file changed

+60
-35
lines changed

webhook.go

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import (
44
"bytes"
55
"errors"
66
"regexp"
7-
"strings"
87

98
"github.com/requilence/integram"
109
)
1110

1211
var m = integram.HTMLRichText{}
12+
var markdownRichText = integram.MarkdownRichText{}
1313

1414
type Config struct{
1515
integram.BotConfig
@@ -20,16 +20,17 @@ type webhook struct {
2020
Mrkdwn bool
2121
Channel string
2222
Attachments []struct {
23-
Pretext string `json:"pretext"`
24-
Fallback string `json:"fallback"`
25-
AuthorName string `json:"author_name"`
26-
AuthorLink string `json:"author_link"`
27-
Title string `json:"title"`
28-
TitleLink string `json:"title_link"`
29-
Text string `json:"text"`
30-
ImageURL string `json:"image_url"`
31-
ThumbURL string `json:"thumb_url"`
32-
Ts int `json:"ts"`
23+
Pretext string `json:"pretext"`
24+
Fallback string `json:"fallback"`
25+
AuthorName string `json:"author_name"`
26+
AuthorLink string `json:"author_link"`
27+
Title string `json:"title"`
28+
TitleLink string `json:"title_link"`
29+
Text string `json:"text"`
30+
MrkdwnIn []string `json:"mrkdwn_in"`
31+
ImageURL string `json:"image_url"`
32+
ThumbURL string `json:"thumb_url"`
33+
Ts int `json:"ts"`
3334
} `json:"attachments"`
3435
}
3536

@@ -63,48 +64,62 @@ func update(c *integram.Context) error {
6364
return nil
6465
}
6566

66-
func escapeTags(text string) string {
67-
return strings.Replace(strings.Replace(text, "<", "&lt;", -1), ">", "&gt;", -1)
68-
}
69-
70-
func convertToMarkdown(text string) string {
71-
// Escape URL links if outside code blocks.
72-
// Message format is documented at https://api.slack.com/docs/message-formatting)
73-
// References to a Slack channel (@), user (#) or variable (!) are kept as-is
74-
linkOrCodeBlockRegexp := regexp.MustCompile("```.+```|`[^`\n]+`|<([^@#!\n][^|>\n]*)(|[^>\n]+)?>")
75-
76-
submatches := linkOrCodeBlockRegexp.FindAllStringSubmatchIndex(text, -1)
67+
func convertLinks(text string, regex *regexp.Regexp, encodeEntities func(string) string, linkFormat func(string, string) string) string {
68+
if encodeEntities == nil {
69+
encodeEntities = func(text string) string {
70+
return text
71+
}
72+
}
73+
submatches := regex.FindAllStringSubmatchIndex(text, -1)
7774
if submatches == nil {
78-
return escapeTags(text)
75+
return encodeEntities(text)
7976
}
8077

8178
convertedBuffer := new(bytes.Buffer)
8279
convertedBuffer.Grow(len(text))
8380
currentPosition := 0
8481
for _, submatch := range submatches {
8582
if submatch[0] > 0 {
86-
convertedBuffer.WriteString(escapeTags(text[currentPosition:submatch[0]]))
83+
convertedBuffer.WriteString(encodeEntities(text[currentPosition:submatch[0]]))
8784
}
8885
if submatch[2] < 0 {
8986
// Code block, copy as-is
90-
convertedBuffer.WriteString(text[submatch[0]:submatch[1]])
87+
convertedBuffer.WriteString(encodeEntities(text[submatch[0]:submatch[1]]))
9188
} else {
92-
// URL link, convert to Markdown
89+
// URL link, convert
9390
url := text[submatch[2]:submatch[3]]
9491
displayText := url
9592
if submatch[4] > 0 && submatch[4] != submatch[5] {
9693
displayText = text[submatch[4] + 1:submatch[5]]
9794
}
98-
convertedBuffer.WriteString("[" + displayText + "](" + url + ")")
95+
convertedBuffer.WriteString(linkFormat(displayText, url))
9996
}
10097
currentPosition = submatch[1]
10198
}
10299
if currentPosition < len(text) {
103-
convertedBuffer.WriteString(escapeTags(text[currentPosition:]))
100+
convertedBuffer.WriteString(encodeEntities(text[currentPosition:]))
104101
}
105102
return convertedBuffer.String()
106103
}
107104

105+
func convertLinksToMarkdown(text string) string {
106+
// Escape URL links if outside code blocks.
107+
// Message format is documented at https://api.slack.com/docs/message-formatting)
108+
// References to a Slack channel (@), user (#) or variable (!) are kept as-is
109+
linkOrCodeBlockRegexp := regexp.MustCompile("```.+```|`[^`\n]+`|<([^@#! \n][^|> \n]*)(|[^>\n]+)?>")
110+
return convertLinks(text, linkOrCodeBlockRegexp, nil, markdownRichText.URL);
111+
}
112+
113+
func convertLinksToHtml(text string) string {
114+
linkOrCodeBlockRegexp := regexp.MustCompile("<code>.*</code>|<pre>.*</pre>|<([^@#! \n][^|> \n]*)(|[^>\n]+)?>")
115+
return convertLinks(text, linkOrCodeBlockRegexp, nil, m.URL);
116+
}
117+
118+
func convertPlainWithLinksToHTML(text string) string {
119+
linkRegexp := regexp.MustCompile("<([^@#! \n][^|> \n]*)(|[^>\n]+)?>")
120+
return convertLinks(text, linkRegexp, m.EncodeEntities, m.URL);
121+
}
122+
108123
func webhookHandler(c *integram.Context, wc *integram.WebhookContext) (err error) {
109124

110125
wh := webhook{Mrkdwn: true}
@@ -114,10 +129,6 @@ func webhookHandler(c *integram.Context, wc *integram.WebhookContext) (err error
114129
return
115130
}
116131

117-
if wh.Mrkdwn {
118-
wh.Text = convertToMarkdown(wh.Text);
119-
}
120-
121132
if len(wh.Attachments) > 0 {
122133
if wh.Text != "" {
123134
wh.Text += "\n"
@@ -130,6 +141,7 @@ func webhookHandler(c *integram.Context, wc *integram.WebhookContext) (err error
130141
}
131142

132143
haveAttachmentWithText:=false
144+
haveMrkdwnAttachment:=false
133145
for i, attachment := range wh.Attachments {
134146
if i > 0 {
135147
text += "\n"
@@ -148,19 +160,32 @@ func webhookHandler(c *integram.Context, wc *integram.WebhookContext) (err error
148160
}
149161

150162
haveAttachmentWithText = true
163+
for _, field := range attachment.MrkdwnIn {
164+
if field == "pretext" {
165+
haveMrkdwnAttachment = true
166+
}
167+
}
151168

152169
text += attachment.Pretext
153170
}
154171

155172
if haveAttachmentWithText {
156-
return c.NewMessage().SetText(convertToMarkdown(text)).EnableMarkdown().Send()
173+
m := c.NewMessage().EnableAntiFlood()
174+
if haveMrkdwnAttachment {
175+
m.SetText(convertLinksToMarkdown(text)).EnableMarkdown()
176+
} else {
177+
m.SetText(convertLinksToHtml(text)).EnableHTML()
178+
}
179+
return m.Send()
157180
}
158181
}
159182

160183
if wh.Text != "" {
161-
m := c.NewMessage().SetText(wh.Text + " " + wh.Channel)
184+
m := c.NewMessage().EnableAntiFlood()
162185
if wh.Mrkdwn {
163-
m.EnableMarkdown()
186+
m.SetText(convertLinksToMarkdown(wh.Text + " " + wh.Channel)).EnableMarkdown()
187+
} else {
188+
m.SetText(convertPlainWithLinksToHTML(wh.Text + " " + wh.Channel)).EnableHTML()
164189
}
165190
return m.Send()
166191
}

0 commit comments

Comments
 (0)