Browse Source

slackapi enhancements, use html/template properly

pull/68/head
Aviral Dasgupta 8 years ago
parent
commit
67c68dbd6a
  1. 204
      src/github.com/matrix-org/go-neb/services/slackapi/message.go
  2. 21
      src/github.com/matrix-org/go-neb/services/slackapi/slackapi.go

204
src/github.com/matrix-org/go-neb/services/slackapi/message.go

@ -8,37 +8,44 @@ import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/matrix"
"github.com/russross/blackfriday" "github.com/russross/blackfriday"
"html/template"
"io/ioutil" "io/ioutil"
"mime" "mime"
"net/http" "net/http"
"regexp" "regexp"
"text/template"
"time" "time"
) )
type slackAttachment struct { type slackAttachment struct {
Fallback string `json:"fallback"`
Color *string `json:"color"`
Pretext string `json:"pretext"`
AuthorName *string `json:"author_name"`
AuthorLink *string `json:"author_link"`
AuthorIcon *string `json:"author_icon"`
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"` Title *string `json:"title"`
TitleLink *string `json:"title_link"` TitleLink *string `json:"title_link"`
Text string `json:"text"`
Text string `json:"text"`
TextRendered template.HTML
MrkdwnIn []string `json:"mrkdwn_in"` MrkdwnIn []string `json:"mrkdwn_in"`
Ts *int64 `json:"ts"` Ts *int64 `json:"ts"`
} }
type slackMessage struct { type slackMessage struct {
Text string `json:"text"`
Username string `json:"username"`
Channel string `json:"channel"`
Mrkdwn *bool `json:"mrkdwn"`
Attachments []slackAttachment `json:"attachments"`
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 // We use text.template because any fields of any attachments could
@ -46,19 +53,31 @@ type slackMessage struct {
// We do not do this yet, since it's assumed that clients also escape the content we send them. // 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(` var htmlTemplate, _ = template.New("htmlTemplate").Parse(`
<strong>@{{ .Username }}</strong> via <strong>#{{ .Channel }}</strong><br /> <strong>@{{ .Username }}</strong> via <strong>#{{ .Channel }}</strong><br />
{{ if .Text }}{{ .Text }}<br />{{ end }}
{{ range .Attachments }}
{{ if .AuthorName }}
{{if .AuthorLink }}<a href="{{ .AuthorLink }}">{{ end }}
{{ if .AuthorIcon }}{{ .AuthorIcon }}{{ end }}
{{ .AuthorName }}
{{if .AuthorLink }}</a>{{ end }}
{{- 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 /> <br />
{{ end }}
<strong><font color="{{ .Color }}"></font>{{ if .TitleLink }}<a href="{{ .TitleLink}}">{{ .Title }}</a>{{ else }}{{ .Title }}{{ end }}<br /></strong>
{{ if .Pretext }}{{ .Pretext }}<br />{{ end }}
{{ if .Text }}{{ .Text }}<br />{{ end }}
{{ end }}
{{- 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{ var netClient = &http.Client{
@ -80,7 +99,7 @@ func getSlackMessage(req http.Request) (message slackMessage, err error) {
decoder := json.NewDecoder(req.Body) decoder := json.NewDecoder(req.Body)
err = decoder.Decode(&message) err = decoder.Decode(&message)
} else { } else {
message.Text = fmt.Sprint("**Error:** unknown Content-Type `%s`", ct)
message.Text = fmt.Sprintf("**Error:** unknown Content-Type `%s`", ct)
log.Error(message.Text) log.Error(message.Text)
} }
@ -92,76 +111,99 @@ func linkifyString(text string) string {
} }
func getColor(color *string) string { func getColor(color *string) string {
if color != nil {
// https://api.slack.com/docs/message-attachments defines these aliases
mappedColor, ok := map[string]string{
"good": "green",
"warning": "yellow",
"danger": "red",
}[*color]
if ok {
return mappedColor
}
return *color
if color == nil {
return "black"
} }
return "black"
// https://api.slack.com/docs/message-attachments defines these aliases
mappedColor, ok := map[string]string{
"good": "green",
"warning": "yellow",
"danger": "red",
}[*color]
if ok {
return mappedColor
}
return *color
} }
func slackMessageToHTMLMessage(message slackMessage) (html matrix.HTMLMessage, err error) {
processedMessage := message
// fetches an image and encodes it as a data URL
// returns nil if fetch fails
func fetchAndEncodeImage(url *string) (data template.URL) {
if url == nil {
return
}
if message.Mrkdwn == nil || *message.Mrkdwn == true {
text := linkifyString(message.Text)
var resp *http.Response
resp, err := netClient.Get(*url)
if err == nil {
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
}
processedMessage.Text = string(blackfriday.MarkdownBasic([]byte(text)))
func renderSlackAttachment(attachment *slackAttachment) {
if attachment == nil {
return
} }
for attachmentID, attachment := range message.Attachments {
target := &processedMessage.Attachments[attachmentID]
color := getColor(attachment.Color)
target.Color = &color
if attachment.AuthorIcon != nil {
var resp *http.Response
resp, err = netClient.Get(*attachment.AuthorIcon)
if err == nil {
body, _ := ioutil.ReadAll(resp.Body)
ct, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
b64body := base64.StdEncoding.EncodeToString(body)
*target.AuthorIcon = fmt.Sprintf("<img src=\"data:%s;base64,%s\" />", ct, b64body)
} else {
*target.AuthorIcon = ""
}
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
} }
for _, fieldName := range attachment.MrkdwnIn {
var targetField, srcField *string
switch fieldName {
case "text":
srcField = &attachment.Text
targetField = &target.Text
break
case "pretext":
srcField = &attachment.Pretext
targetField = &target.Pretext
break
}
if targetField != nil && srcField != nil {
value := string(
blackfriday.MarkdownBasic([]byte(linkifyString(*srcField))))
targetField = &value
}
if targetField != nil && srcField != nil {
log.Info(targetField)
*targetField = template.HTML(
blackfriday.MarkdownBasic([]byte(linkifyString(*srcField))))
} }
} }
}
func slackMessageToHTMLMessage(message slackMessage) (html matrix.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 var buffer bytes.Buffer
html.MsgType = "m.text" html.MsgType = "m.text"
html.Format = "org.matrix.custom.html" html.Format = "org.matrix.custom.html"
html.Body, _ = slackMessageToMarkdown(message) html.Body, _ = slackMessageToMarkdown(message)
err = htmlTemplate.ExecuteTemplate(&buffer, "htmlTemplate", processedMessage)
err = htmlTemplate.ExecuteTemplate(&buffer, "htmlTemplate", message)
html.FormattedBody = buffer.String() html.FormattedBody = buffer.String()
return return
} }

21
src/github.com/matrix-org/go-neb/services/slackapi/slackapi.go

@ -13,7 +13,11 @@ type slackAPIService struct {
serviceUserID string serviceUserID string
webhookEndpointURL string webhookEndpointURL string
ClientUserID string ClientUserID string
Hooks map[string]string
// maps from hookID -> roomID
Hooks map[string]struct {
RoomID string
MessageType string
}
} }
func (s *slackAPIService) ServiceUserID() string { return s.serviceUserID } func (s *slackAPIService) ServiceUserID() string { return s.serviceUserID }
@ -30,15 +34,26 @@ func (s *slackAPIService) Plugin(cli *matrix.Client, roomID string) plugin.Plugi
func (s *slackAPIService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *matrix.Client) { func (s *slackAPIService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *matrix.Client) {
segments := strings.Split(req.URL.Path, "/") segments := strings.Split(req.URL.Path, "/")
if len(segments) < 2 {
w.WriteHeader(400)
}
hookID := segments[len(segments)-2] hookID := segments[len(segments)-2]
messageType := segments[len(segments)-3]
roomID := s.Hooks[hookID]
messageType := s.Hooks[hookID].MessageType
if messageType == "" {
messageType = "m.text"
}
roomID := s.Hooks[hookID].RoomID
slackMessage, err := getSlackMessage(*req) slackMessage, err := getSlackMessage(*req)
if err != nil { if err != nil {
return return
} }
htmlMessage, err := slackMessageToHTMLMessage(slackMessage) htmlMessage, err := slackMessageToHTMLMessage(slackMessage)
if err != nil {
return
}
htmlMessage.MsgType = messageType htmlMessage.MsgType = messageType
cli.SendMessageEvent( cli.SendMessageEvent(
roomID, roomID,

Loading…
Cancel
Save