mirror of https://github.com/matrix-org/go-neb.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
5.9 KiB
225 lines
5.9 KiB
package slackapi
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"io/ioutil"
|
|
"mime"
|
|
"net/http"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/matrix-org/gomatrix"
|
|
"github.com/russross/blackfriday"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type slackAttachment struct {
|
|
Fallback string `json:"fallback"`
|
|
FallbackRendered template.HTML
|
|
Color *string `json:"color"`
|
|
ColorRendered template.HTMLAttr
|
|
Pretext string `json:"pretext"`
|
|
PretextRendered template.HTML
|
|
|
|
AuthorName *string `json:"author_name"`
|
|
AuthorLink template.URL `json:"author_link"`
|
|
AuthorIcon *string `json:"author_icon"`
|
|
AuthorIconURL template.URL
|
|
|
|
Title *string `json:"title"`
|
|
TitleLink *string `json:"title_link"`
|
|
|
|
Text string `json:"text"`
|
|
TextRendered template.HTML
|
|
|
|
MrkdwnIn []string `json:"mrkdwn_in"`
|
|
Ts *int64 `json:"ts"`
|
|
}
|
|
|
|
type slackMessage struct {
|
|
Text string `json:"text"`
|
|
TextRendered template.HTML
|
|
Username string `json:"username"`
|
|
Channel string `json:"channel"`
|
|
Mrkdwn *bool `json:"mrkdwn"`
|
|
Attachments []slackAttachment `json:"attachments"`
|
|
}
|
|
|
|
// We use text.template because any fields of any attachments could
|
|
// be Markdown, so it's convenient to escape on a field-by field basis.
|
|
// We do not do this yet, since it's assumed that clients also escape the content we send them.
|
|
var htmlTemplate, _ = template.New("htmlTemplate").Parse(`
|
|
<strong>@{{ .Username }}</strong> via <strong>#{{ .Channel }}</strong><br />
|
|
{{- with (or .TextRendered .Text nil) }}
|
|
{{- if . }}
|
|
{{- . }}<br />
|
|
{{- end }}
|
|
{{- end }}
|
|
{{- range .Attachments }}
|
|
{{- if .AuthorName }}
|
|
{{- if .AuthorLink }}<a href="{{ .AuthorLink }}">{{ end }}
|
|
{{- if .AuthorIconUrl }}<img src="{{ .AuthorIconUrl }}" />{{ end }}
|
|
{{- .AuthorName }}
|
|
{{- if .AuthorLink }}</a>{{ end }}
|
|
<br />
|
|
{{- end }}
|
|
<strong>
|
|
<font color="{{- .ColorRendered }}">▌</font>
|
|
{{- if .TitleLink }}
|
|
<a href="{{ .TitleLink}}">{{ .Title }}</a>
|
|
{{- else }}
|
|
{{- .Title }}
|
|
{{- end }}
|
|
<br />
|
|
</strong>
|
|
{{- if .Pretext }}{{ or .PretextRendered .Pretext }}<br />{{ end }}
|
|
{{- if .Text }}{{ or .TextRendered .Text }}<br />{{ end }}
|
|
{{- end }}
|
|
`)
|
|
|
|
var netClient = &http.Client{
|
|
Timeout: time.Second * 10,
|
|
}
|
|
|
|
// TODO: What does this do?
|
|
var linkRegex, _ = regexp.Compile("<([^|]+)(\\|([^>]+))?>")
|
|
|
|
func getSlackMessage(req http.Request) (message slackMessage, err error) {
|
|
ct := req.Header.Get("Content-Type")
|
|
ct, _, err = mime.ParseMediaType(ct)
|
|
|
|
if ct == "application/x-www-form-urlencoded" {
|
|
req.ParseForm()
|
|
payload := req.Form.Get("payload")
|
|
err = json.Unmarshal([]byte(payload), &message)
|
|
} else if ct == "application/json" {
|
|
decoder := json.NewDecoder(req.Body)
|
|
err = decoder.Decode(&message)
|
|
} else {
|
|
message.Text = fmt.Sprintf("**Error:** unknown Content-Type `%s`", ct)
|
|
log.Error(message.Text)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func linkifyString(text string) string {
|
|
return linkRegex.ReplaceAllString(text, "<a href=\"$1\">$3</a>")
|
|
}
|
|
|
|
// Convert a Slack colour (defined at https://api.slack.com/docs/message-attachments )
|
|
// into an HTML color.
|
|
func getColor(color *string) string {
|
|
if color == nil {
|
|
return "black"
|
|
}
|
|
|
|
mappedColor, ok := map[string]string{
|
|
"good": "green",
|
|
"warning": "yellow",
|
|
"danger": "red",
|
|
}[*color]
|
|
if ok {
|
|
return mappedColor
|
|
}
|
|
|
|
// HTML color= attributes support any arbitrary string, so just pass through.
|
|
return *color
|
|
}
|
|
|
|
// fetches an image and encodes it as a data URL
|
|
// returns an empty string if fetch fails
|
|
func fetchAndEncodeImage(url *string) (data template.URL) {
|
|
if url == nil {
|
|
return
|
|
}
|
|
|
|
var resp *http.Response
|
|
resp, err := netClient.Get(*url)
|
|
if err != nil {
|
|
log.WithError(err).WithField("url", url).Error("Failed to GET URL")
|
|
return
|
|
}
|
|
|
|
var (
|
|
body []byte
|
|
contentType string
|
|
)
|
|
|
|
if body, err = ioutil.ReadAll(resp.Body); err != nil {
|
|
return
|
|
}
|
|
if contentType, _, err = mime.ParseMediaType(resp.Header.Get("Content-Type")); err != nil {
|
|
return
|
|
}
|
|
base64Body := base64.StdEncoding.EncodeToString(body)
|
|
data = template.URL(fmt.Sprintf("data:%s;base64,%s", contentType, base64Body))
|
|
|
|
return
|
|
}
|
|
|
|
func renderSlackAttachment(attachment *slackAttachment) {
|
|
if attachment == nil {
|
|
return
|
|
}
|
|
|
|
attachment.ColorRendered = template.HTMLAttr(getColor(attachment.Color))
|
|
attachment.AuthorIconURL = fetchAndEncodeImage(attachment.AuthorIcon)
|
|
|
|
for _, fieldName := range attachment.MrkdwnIn {
|
|
var (
|
|
srcField *string
|
|
targetField *template.HTML
|
|
)
|
|
|
|
switch fieldName {
|
|
case "text":
|
|
srcField = &attachment.Text
|
|
targetField = &attachment.TextRendered
|
|
case "pretext":
|
|
srcField = &attachment.Pretext
|
|
targetField = &attachment.PretextRendered
|
|
case "fallback":
|
|
srcField = &attachment.Fallback
|
|
targetField = &attachment.FallbackRendered
|
|
}
|
|
|
|
if targetField != nil && srcField != nil {
|
|
*targetField = template.HTML(
|
|
blackfriday.MarkdownBasic([]byte(linkifyString(*srcField))))
|
|
}
|
|
}
|
|
}
|
|
|
|
func slackMessageToHTMLMessage(message slackMessage) (html gomatrix.HTMLMessage, err error) {
|
|
text := linkifyString(message.Text)
|
|
if message.Mrkdwn == nil || *message.Mrkdwn == true {
|
|
message.TextRendered = template.HTML(blackfriday.MarkdownBasic([]byte(text)))
|
|
}
|
|
|
|
for attachmentID := range message.Attachments {
|
|
renderSlackAttachment(&message.Attachments[attachmentID])
|
|
}
|
|
|
|
var buffer bytes.Buffer
|
|
html.MsgType = "m.text"
|
|
html.Format = "org.matrix.custom.html"
|
|
html.Body, _ = slackMessageToMarkdown(message)
|
|
err = htmlTemplate.ExecuteTemplate(&buffer, "htmlTemplate", message)
|
|
html.FormattedBody = buffer.String()
|
|
return
|
|
}
|
|
|
|
// This can be improved; Markdown does support all of Slack's formatting
|
|
// Which we're just throwing away at the moment.
|
|
func slackMessageToMarkdown(message slackMessage) (markdown string, err error) {
|
|
markdown += message.Text + "\n"
|
|
for _, attachment := range message.Attachments {
|
|
markdown += attachment.Fallback + "\n"
|
|
}
|
|
return
|
|
}
|