-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(services): add zulip chat service
- Loading branch information
Showing
8 changed files
with
403 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Zulip Chat | ||
|
||
## URL format | ||
|
||
The shoutrrr service URL should look like this: | ||
> zulip://__`bot-mail`__:__`bot-key`__@__`zulip-domain`__/?stream=__`name-or-id`__&topic=__`name`__ | ||
Stream and topic are both optional and can be given as parameters to the Send method: | ||
|
||
```go | ||
sender, __ := shoutrrr.CreateSender(url) | ||
|
||
params := make(types.Params) | ||
params["stream"] = "mystream" | ||
params["topic"] = "This is my topic" | ||
|
||
sender.Send(message, ¶ms) | ||
``` | ||
|
||
Since __`bot-mail`__ is a mail address you need to URL escape the `@` in it to `%40`. | ||
|
||
An example service URL would look like: | ||
> zulip://my-bot%40zulipchat.com:correcthorsebatterystable@example.zulipchat.com?stream=foo&topic=bar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package zulip | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/containrrr/shoutrrr/pkg/services/standard" | ||
"github.com/containrrr/shoutrrr/pkg/types" | ||
) | ||
|
||
// Service sends notifications to a pre-configured channel or user | ||
type Service struct { | ||
standard.Standard | ||
config *Config | ||
} | ||
|
||
const ( | ||
contentMaxSize = 10000 // bytes | ||
topicMaxLength = 60 // characters | ||
) | ||
|
||
// Send a notification message to Zulip | ||
func (service *Service) Send(message string, params *types.Params) error { | ||
// Clone the config because we might modify stream and/or | ||
// topic with values from the parameters and they should only | ||
// change this Send(). | ||
config := service.config.Clone() | ||
|
||
if params != nil { | ||
if stream, found := (*params)["stream"]; found { | ||
config.Stream = stream | ||
} | ||
|
||
if topic, found := (*params)["topic"]; found { | ||
config.Topic = topic | ||
} | ||
} | ||
|
||
topicLength := len([]rune(config.Topic)) | ||
|
||
if topicLength > topicMaxLength { | ||
return fmt.Errorf(string(TopicTooLong), topicMaxLength, topicLength) | ||
} | ||
|
||
messageSize := len(message) | ||
|
||
if messageSize > contentMaxSize { | ||
return fmt.Errorf("message exceeds max size (%d bytes): was %d bytes", contentMaxSize, messageSize) | ||
} | ||
|
||
return service.doSend(config, message) | ||
} | ||
|
||
// Initialize loads ServiceConfig from configURL and sets logger for this Service | ||
func (service *Service) Initialize(configURL *url.URL, logger *log.Logger) error { | ||
service.Logger.SetLogger(logger) | ||
service.config = &Config{} | ||
|
||
if err := service.config.SetURL(configURL); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (service *Service) doSend(config *Config, message string) error { | ||
apiURL := service.getURL(config) | ||
payload := CreatePayload(config, message) | ||
res, err := http.Post(apiURL, "application/x-www-form-urlencoded", strings.NewReader(payload.Encode())) | ||
|
||
if res.StatusCode != http.StatusOK { | ||
return fmt.Errorf("failed to send notification to service, response status code %s", res.Status) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (service *Service) getURL(config *Config) string { | ||
url := url.URL{ | ||
User: url.UserPassword(config.BotMail, config.BotKey), | ||
Host: config.Host, | ||
Path: config.Path, | ||
Scheme: "https", | ||
} | ||
|
||
return url.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package zulip | ||
|
||
import ( | ||
"errors" | ||
"net/url" | ||
) | ||
|
||
// Config for the zulip service | ||
type Config struct { | ||
BotMail string | ||
BotKey string | ||
Host string | ||
Path string | ||
Stream string | ||
Topic string | ||
} | ||
|
||
// GetURL returns a URL representation of it's current field values | ||
func (config *Config) GetURL() *url.URL { | ||
query := &url.Values{} | ||
|
||
if config.Stream != "" { | ||
query.Set("stream", config.Stream) | ||
} | ||
|
||
if config.Topic != "" { | ||
query.Set("topic", config.Topic) | ||
} | ||
|
||
return &url.URL{ | ||
User: url.UserPassword(config.BotMail, config.BotKey), | ||
Host: config.Host, | ||
Path: config.Path, | ||
RawQuery: query.Encode(), | ||
Scheme: Scheme, | ||
} | ||
} | ||
|
||
// SetURL updates a ServiceConfig from a URL representation of it's field values | ||
func (config *Config) SetURL(serviceURL *url.URL) error { | ||
var ok bool | ||
|
||
config.BotMail = serviceURL.User.Username() | ||
|
||
if config.BotMail == "" { | ||
return errors.New(string(MissingBotMail)) | ||
} | ||
|
||
config.BotKey, ok = serviceURL.User.Password() | ||
|
||
if !ok { | ||
return errors.New(string(MissingAPIKey)) | ||
} | ||
|
||
config.Host = serviceURL.Hostname() | ||
|
||
if config.Host == "" { | ||
return errors.New(string(MissingHost)) | ||
} | ||
|
||
config.Path = "api/v1/messages" | ||
config.Stream = serviceURL.Query().Get("stream") | ||
config.Topic = serviceURL.Query().Get("topic") | ||
|
||
return nil | ||
} | ||
|
||
// Clone the config to a new Config struct | ||
func (config *Config) Clone() *Config { | ||
return &Config{ | ||
BotMail: config.BotMail, | ||
BotKey: config.BotKey, | ||
Host: config.Host, | ||
Path: config.Path, | ||
Stream: config.Stream, | ||
Topic: config.Topic, | ||
} | ||
} | ||
|
||
const ( | ||
// Scheme is the identifying part of this service's configuration URL | ||
Scheme = "zulip" | ||
) | ||
|
||
// CreateConfigFromURL to use within the zulip service | ||
func CreateConfigFromURL(serviceURL *url.URL) (*Config, error) { | ||
config := Config{} | ||
err := config.SetURL(serviceURL) | ||
|
||
return &config, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package zulip | ||
|
||
// ErrorMessage for error events within the zulip service | ||
type ErrorMessage string | ||
|
||
const ( | ||
// MissingAPIKey from the service URL | ||
MissingAPIKey ErrorMessage = "missing API key" | ||
// MissingHost from the service URL | ||
MissingHost ErrorMessage = "missing Zulip host" | ||
// MissingBotMail from the service URL | ||
MissingBotMail ErrorMessage = "missing Bot mail address" | ||
// TopicTooLong if topic is more than 60 characters | ||
TopicTooLong ErrorMessage = "topic exceeds max length (%d characters): was %d characters" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package zulip | ||
|
||
import ( | ||
"net/url" | ||
) | ||
|
||
// CreatePayload compatible with the zulip api | ||
func CreatePayload(config *Config, message string) url.Values { | ||
form := url.Values{} | ||
form.Set("type", "stream") | ||
form.Set("to", config.Stream) | ||
form.Set("content", message) | ||
|
||
if config.Topic != "" { | ||
form.Set("topic", config.Topic) | ||
} | ||
|
||
return form | ||
} |
Oops, something went wrong.