mirror of
https://github.com/hanzoai/notify.git
synced 2026-10-30 15:04:32 +00:00
154 lines
4.6 KiB
Go
154 lines
4.6 KiB
Go
// Package mattermost provides message notification integration for mattermost.com.
|
|
package mattermost
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
stdhttp "net/http"
|
|
|
|
"github.com/hanzoai/notify/service/http"
|
|
)
|
|
|
|
type httpClient interface {
|
|
AddReceivers(wh ...*http.Webhook)
|
|
PreSend(prefn http.PreSendHookFn)
|
|
Send(ctx context.Context, subject, message string) error
|
|
PostSend(postfn http.PostSendHookFn)
|
|
}
|
|
|
|
// Service encapsulates the notify httpService client and contains mattermost channel ids.
|
|
type Service struct {
|
|
loginClient httpClient
|
|
messageClient httpClient
|
|
channelIDs map[string]bool
|
|
}
|
|
|
|
// New returns a new instance of a Mattermost notification service.
|
|
func New(url string) *Service {
|
|
httpService := setupMsgService(url)
|
|
return &Service{
|
|
setupLoginService(url, httpService),
|
|
httpService,
|
|
make(map[string]bool),
|
|
}
|
|
}
|
|
|
|
// LoginWithCredentials provides helper for authentication using Mattermost user/admin credentials.
|
|
func (s *Service) LoginWithCredentials(ctx context.Context, loginID, password string) error {
|
|
return s.loginClient.Send(ctx, loginID, password)
|
|
}
|
|
|
|
// AddReceivers takes Mattermost channel IDs or Chat IDs and adds them to the internal channel ID list.
|
|
// The Send method will send a given message to all these channels.
|
|
func (s *Service) AddReceivers(channelIDs ...string) {
|
|
for i := range channelIDs {
|
|
s.channelIDs[channelIDs[i]] = true
|
|
}
|
|
}
|
|
|
|
// Send takes a message subject and a message body and send them to added channel ids.
|
|
// you will need a 'create_post' permission for your username.
|
|
// refer https://api.mattermost.com/ for more info.
|
|
func (s *Service) Send(ctx context.Context, subject, message string) error {
|
|
for id := range s.channelIDs {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
// create post
|
|
if err := s.messageClient.Send(ctx, id, subject+"\n"+message); err != nil {
|
|
return fmt.Errorf("send message to channel %q: %w", id, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PreSend adds a pre-send hook to the service. The hook will be executed before sending a request to a receiver.
|
|
func (s *Service) PreSend(hook http.PreSendHookFn) {
|
|
s.messageClient.PreSend(hook)
|
|
}
|
|
|
|
// PostSend adds a post-send hook to the service. The hook will be executed after sending a request to a receiver.
|
|
func (s *Service) PostSend(hook http.PostSendHookFn) {
|
|
s.messageClient.PostSend(hook)
|
|
}
|
|
|
|
// setups main message service for creating posts.
|
|
func setupMsgService(url string) *http.Service {
|
|
// create new http client for sending messages/notifications
|
|
httpService := http.New()
|
|
|
|
// add custom payload builder
|
|
httpService.AddReceivers(&http.Webhook{
|
|
URL: url + "/api/v4/posts",
|
|
Header: stdhttp.Header{},
|
|
ContentType: "application/json",
|
|
Method: stdhttp.MethodPost,
|
|
BuildPayload: func(channelID, subjectAndMessage string) any {
|
|
return map[string]string{
|
|
"channel_id": channelID,
|
|
"message": subjectAndMessage,
|
|
}
|
|
},
|
|
})
|
|
|
|
// add post-send hook for error checks
|
|
httpService.PostSend(func(_ *stdhttp.Request, resp *stdhttp.Response) error {
|
|
if resp.StatusCode != stdhttp.StatusCreated {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("create post failed with status: %s body: %s", resp.Status, string(b))
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return httpService
|
|
}
|
|
|
|
// setups login service to get token.
|
|
func setupLoginService(url string, msgService *http.Service) *http.Service {
|
|
// create another new http client for login request call.
|
|
httpService := http.New()
|
|
|
|
// append login path for the given mattermost server with custom payload builder.
|
|
httpService.AddReceivers(&http.Webhook{
|
|
URL: url + "/api/v4/users/login",
|
|
Header: stdhttp.Header{},
|
|
ContentType: "application/json",
|
|
Method: stdhttp.MethodPost,
|
|
BuildPayload: func(loginID, password string) any {
|
|
return map[string]string{
|
|
"login_id": loginID,
|
|
"password": password,
|
|
}
|
|
},
|
|
})
|
|
|
|
// Add post-send hook to do error checks and log the response after it is received.
|
|
// Also extract token from response header and set it as part of pre-send hook of main http client for further
|
|
// requests.
|
|
httpService.PostSend(func(_ *stdhttp.Request, resp *stdhttp.Response) error {
|
|
if resp.StatusCode != stdhttp.StatusOK {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("login failed with status: %s body: %s", resp.Status, string(b))
|
|
}
|
|
|
|
// get token from header
|
|
token := resp.Header.Get("Token")
|
|
if token == "" {
|
|
return errors.New("received empty token")
|
|
}
|
|
|
|
// set token as pre-send hook
|
|
msgService.PreSend(func(req *stdhttp.Request) error {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
return nil
|
|
})
|
|
return nil
|
|
})
|
|
return httpService
|
|
}
|