Skip to content

Commit ccab90f

Browse files
committed
feat(service): Added Zulip service support, closes #457
1 parent 999c6ff commit ccab90f

File tree

8 files changed

+476
-0
lines changed

8 files changed

+476
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ Yes, please! Contributions of all kinds are very welcome! Feel free to check our
110110
| [WeChat](https://www.wechat.com) | [service/wechat](service/wechat) | [silenceper/wechat](https://github.com/silenceper/wechat) | :heavy_check_mark: |
111111
| [Webpush Notification](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) | [service/webpush](service/webpush) | [SherClockHolmes/webpush-go](https://github.com/SherClockHolmes/webpush-go/) | :heavy_check_mark: |
112112
| [WhatsApp](https://www.whatsapp.com) | [service/whatsapp](service/whatsapp) | [Rhymen/go-whatsapp](https://github.com/Rhymen/go-whatsapp) | :x: |
113+
| [Zulip](https://zulip.com/) | [service/zulip](service/zulip) | [ifo/gozulipbot](github.com/ifo/gozulipbot) | :heavy_check_mark: |
113114

114115
## Special Thanks <a id="special_thanks"></a>
115116

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ require (
4242
require github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
4343

4444
require (
45+
github.com/ifo/gozulipbot v0.0.1
4546
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
4647
github.com/vartanbeno/go-reddit/v2 v2.0.1
4748
google.golang.org/api v0.143.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslC
187187
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
188188
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
189189
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
190+
github.com/ifo/gozulipbot v0.0.1 h1:hYcUViKBe1ZIXk+WRNOe2dEFGi6H8G1cr25HsEnN9Xw=
191+
github.com/ifo/gozulipbot v0.0.1/go.mod h1:KBDdzKbjflzh+LBaYauJmuDCwwXfFLI6j3eSENlScE0=
190192
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
191193
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
192194
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=

service/zulip/common.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package zulip
2+
3+
// Receiver encapsulates a receiver credentials for a direct or stream message.
4+
type Receiver struct {
5+
email string
6+
stream string
7+
topic string
8+
}
9+
10+
// Direct specifies a Zulip Direct message
11+
func Direct(email string) *Receiver {
12+
return &Receiver{email: email}
13+
}
14+
15+
// Stream specifies a Zulip Stream message
16+
func Stream(stream, topic string) *Receiver {
17+
return &Receiver{stream: stream, topic: topic}
18+
}
19+
20+
type ErrorResponse struct {
21+
Code string `json:"code"`
22+
Message string `json:"msg"`
23+
Result string `json:"result"`
24+
}

service/zulip/mock_zulip_client.go

Lines changed: 56 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

service/zulip/usage.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Zulip Usage
2+
3+
Ensure that you have already navigated to your GOPATH and installed the following packages:
4+
5+
* `go get -u github.com/nikoksr/notify`
6+
7+
## Steps for creating Zulip Bot
8+
9+
These are general and very high level instructions
10+
11+
1. Create a new Zulip bot (https://zulip.com/help/add-a-bot-or-integration)
12+
2. Copy your *Organization URL* from the browser address bar. You need to copy only subdomain `your-org` from the full url `your-org.zulipchat.com` without the hostname `.zulipchat.com`.
13+
3. Copy your *Bot Email* and *API Key* for usage below
14+
4. Copy the *Stream name* of the stream if you want to post a message to stream or just copy an email address of the receiver.
15+
5. Now you should be good to use the code below
16+
17+
## Sample Code
18+
19+
```go
20+
package main
21+
22+
import (
23+
"context"
24+
"fmt"
25+
"github.com/nikoksr/notify"
26+
"github.com/nikoksr/notify/service/zulip"
27+
)
28+
29+
func main() {
30+
31+
notifier := notify.New()
32+
33+
// Provide your Zulip Bot credentials
34+
zulipService := zulip.New(
35+
"your-org",
36+
"ZULIP_API_KEY",
37+
"email-bot@your-org.zulipchat.com",
38+
)
39+
40+
// Passing a Zulip receivers as a receiver for our messages.
41+
// Where to send our messages.
42+
// It can be direct or stream message
43+
zulipService.AddReceivers(zulip.Direct("some-user@email.com"))
44+
zulipService.AddReceivers(zulip.Stream("alerts", "critical"))
45+
46+
// Tell our notifier to use the Zulip service. You can repeat the above process
47+
// for as many services as you like and just tell the notifier to use them.
48+
notifier.UseServices(zulipService)
49+
50+
// Send a message
51+
err := notifier.Send(
52+
context.Background(),
53+
"Hello from notify :wave:\n",
54+
"Message written in Go!",
55+
)
56+
57+
if err != nil {
58+
fmt.Println(err)
59+
}
60+
61+
}
62+
```

service/zulip/zulip.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package zulip
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
10+
gzb "github.com/ifo/gozulipbot"
11+
"github.com/pkg/errors"
12+
)
13+
14+
//go:generate mockery --name=zulipClient --output=. --case=underscore --inpackage
15+
type zulipClient interface {
16+
Message(gzb.Message) (*http.Response, error)
17+
}
18+
19+
// Compile-time check to ensure that zulip message client implements the zulipClient interface.
20+
var _ zulipClient = new(gzb.Bot)
21+
22+
// Zulip struct holds necessary data to communicate with the Zulip API.
23+
type Zulip struct {
24+
client zulipClient
25+
receivers []*Receiver
26+
}
27+
28+
func New(domain, apiKey, botEmail string) *Zulip {
29+
client := &gzb.Bot{
30+
APIURL: fmt.Sprintf("https://%s.zulipchat.com/api/v1/", domain),
31+
APIKey: apiKey,
32+
Email: botEmail,
33+
}
34+
35+
client.Init()
36+
37+
zulip := &Zulip{
38+
client: client,
39+
receivers: make([]*Receiver, 0),
40+
}
41+
42+
return zulip
43+
}
44+
45+
func (z *Zulip) AddReceivers(receivers ...*Receiver) {
46+
z.receivers = append(z.receivers, receivers...)
47+
}
48+
49+
func (z *Zulip) Send(ctx context.Context, subject, message string) error {
50+
fullMessage := subject + "\n" + message // Treating subject as message title
51+
52+
for _, receiver := range z.receivers {
53+
select {
54+
case <-ctx.Done():
55+
return ctx.Err()
56+
default:
57+
emails := make([]string, 0)
58+
if receiver.email != "" {
59+
emails = append(emails, receiver.email)
60+
}
61+
62+
msg := gzb.Message{
63+
Content: fullMessage,
64+
Emails: emails,
65+
Stream: receiver.stream,
66+
Topic: receiver.topic,
67+
}
68+
69+
resp, err := z.client.Message(msg)
70+
if err != nil {
71+
return errors.Wrapf(err, "failed to send message to Zulip receiver")
72+
}
73+
defer resp.Body.Close()
74+
body, _ := io.ReadAll(resp.Body)
75+
76+
switch resp.StatusCode {
77+
case http.StatusBadRequest:
78+
var errorResp ErrorResponse
79+
_ = json.Unmarshal(body, &errorResp)
80+
81+
return errors.Errorf("failed to send message to Zulip receiver: %s", errorResp.Message)
82+
83+
case http.StatusOK:
84+
break
85+
86+
default:
87+
return errors.Errorf("failed to send message to Zulip receiver: %s", body)
88+
}
89+
}
90+
}
91+
92+
return nil
93+
}

0 commit comments

Comments
 (0)