-
Notifications
You must be signed in to change notification settings - Fork 210
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #300 from immanuelhume/feat/lark
- Loading branch information
Showing
12 changed files
with
589 additions
and
1 deletion.
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
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,99 @@ | ||
# Lark | ||
|
||
## Prerequisites | ||
|
||
Depending on your requirements, you'll need either a custom app or a Lark group | ||
chat webhook. The latter is easier to set up, but can only send messages to the | ||
group it is in. You may refer to the doc | ||
[here](https://open.larksuite.com/document/uAjLw4CM/ukTMukTMuhttps://open.larksuite.com/document/home/develop-a-bot-in-5-minutes/create-an-appkTM/bot-v3/use-custom-bots-in-a-group) | ||
to set up a webhook bot, and the doc | ||
[here](https://open.larksuite.com/document/home/develop-a-bot-in-5-minutes/create-an-app) | ||
to set up a custom app. | ||
|
||
## Usage | ||
|
||
### Webhook | ||
|
||
For webhook bots, we only need the webhook URL, which might look something like | ||
`https://open.feishu.cn/open-apis/bot/v2/hook/xxx`. Note that there is no | ||
method to configure receivers, because the webhook bot can only send messages | ||
to the group in which it was created. | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"context" | ||
"log" | ||
|
||
"github.com/nikoksr/notify" | ||
"github.com/nikoksr/notify/service/lark" | ||
) | ||
|
||
// Replace this with your own webhook URL. | ||
const webHookURL = "https://open.feishu.cn/open-apis/bot/v2/hook/xxx" | ||
|
||
func main() { | ||
larkWebhookSvc := lark.NewWebhookService(webHookURL) | ||
|
||
notifier := notify.New() | ||
notifier.UseServices(larkWebhookSvc) | ||
|
||
if err := notifier.Send(context.Background(), "subject", "message"); err != nil { | ||
log.Fatalf("notifier.Send() failed: %s", err.Error()) | ||
} | ||
|
||
log.Println("notification sent") | ||
} | ||
``` | ||
|
||
### Custom App | ||
|
||
For custom apps, we need to pass in the App ID and App Secret when creating a | ||
new notification service. When adding receivers, the type of the receiver ID | ||
must be specified, as shown in the example below. You may refer to the section | ||
entitled "Query parameters" in the doc | ||
[here](https://open.larksuite.com/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/create) | ||
for more information about the different ID types. | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"context" | ||
"log" | ||
|
||
"github.com/nikoksr/notify" | ||
"github.com/nikoksr/notify/service/lark" | ||
) | ||
|
||
// Replace these with the credentials from your custom app. | ||
const ( | ||
appId = "xxx" | ||
appSecret = "xxx" | ||
) | ||
|
||
func main() { | ||
larkCustomAppService := lark.NewCustomAppService(appId, appSecret) | ||
|
||
// Lark implements five types of receiver IDs. You'll need to specify the | ||
// type using the respective helper functions when adding them as receivers. | ||
larkCustomAppService.AddReceivers( | ||
lark.OpenID("xxx"), | ||
lark.UserID("xxx"), | ||
lark.UnionID("xxx"), | ||
lark.Email("[email protected]"), | ||
lark.ChatID("xxx"), | ||
) | ||
|
||
notifier := notify.New() | ||
notifier.UseServices(larkCustomAppService) | ||
|
||
if err := notifier.Send(context.Background(), "subject", "message"); err != nil { | ||
log.Fatalf("notifier.Send() failed: %s", err.Error()) | ||
} | ||
|
||
log.Println("notification sent") | ||
} | ||
``` | ||
|
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,58 @@ | ||
package lark | ||
|
||
// sender is an interface for sending a message to an already defined receiver. | ||
type sender interface { | ||
Send(subject, message string) error | ||
} | ||
|
||
// sender is an interface for sending a message to a specific receiver ID. | ||
type sendToer interface { | ||
SendTo(subject, message, id, idType string) error | ||
} | ||
|
||
// receiverID encapsulates a receiver ID and its type in Lark. | ||
type receiverID struct { | ||
id string | ||
typ receiverIDType | ||
} | ||
|
||
// OpenID specifies an ID as a Lark Open ID. | ||
func OpenID(s string) *receiverID { | ||
return &receiverID{s, openID} | ||
} | ||
|
||
// UserID specifies an ID as a Lark User ID. | ||
func UserID(s string) *receiverID { | ||
return &receiverID{s, userID} | ||
} | ||
|
||
// UnionID specifies an ID as a Lark Union ID. | ||
func UnionID(s string) *receiverID { | ||
return &receiverID{s, unionID} | ||
} | ||
|
||
// Email specifies a receiver ID as an email. | ||
func Email(s string) *receiverID { | ||
return &receiverID{s, email} | ||
} | ||
|
||
// ChatID specifies an ID as a Lark Chat ID. | ||
func ChatID(s string) *receiverID { | ||
return &receiverID{s, chatID} | ||
} | ||
|
||
// receiverIDType represents the different ID types implemented by Lark. This | ||
// information is required when sending a message. More information about the | ||
// different ID types can be found in the "Query parameters" section of | ||
// the https://open.larksuite.com/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/create, | ||
// or on | ||
// https://open.larksuite.com/document/home/user-identity-introduction/introduction. | ||
type receiverIDType string | ||
|
||
const ( | ||
openID receiverIDType = "open_id" | ||
userID receiverIDType = "user_id" | ||
unionID receiverIDType = "union_id" | ||
email receiverIDType = "email" | ||
chatID receiverIDType = "chat_id" | ||
) |
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,111 @@ | ||
package lark | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/go-lark/lark" | ||
|
||
"github.com/nikoksr/notify" | ||
) | ||
|
||
type customAppService struct { | ||
receiveIDs []*receiverID | ||
cli sendToer | ||
} | ||
|
||
// Compile time check that larkCustomAppService implements notify.Notifer. | ||
var _ notify.Notifier = &customAppService{} | ||
|
||
// NewCustomAppService returns a new instance of a Lark notify service using a | ||
// Lark custom app. | ||
func NewCustomAppService(appID, appSecret string) *customAppService { | ||
bot := lark.NewChatBot(appID, appSecret) | ||
|
||
// We need to set the bot to use Lark's open.larksuite.com domain instead of | ||
// the default open.feishu.cn domain. | ||
bot.SetDomain(lark.DomainLark) | ||
|
||
// Let the bot use a HTTP client with a longer timeout than the default 5 | ||
// seconds. | ||
bot.SetClient(&http.Client{ | ||
Timeout: 8 * time.Second, | ||
}) | ||
|
||
_ = bot.StartHeartbeat() | ||
|
||
return &customAppService{ | ||
receiveIDs: make([]*receiverID, 0), | ||
cli: &larkClientGoLarkChatBot{ | ||
bot: bot, | ||
}, | ||
} | ||
} | ||
|
||
// AddReceivers adds recipients to future notifications. There are five different | ||
// types of receiver IDs available in Lark and they must be specified here. For | ||
// example: | ||
// | ||
// larkService.AddReceivers( | ||
// lark.OpenID("ou_c99c5f35d542efc7ee492afe11af19ef"), | ||
// lark.UserID("8335aga2"), | ||
// lark.UnionID("on_cad4860e7af114fb4ff6c5d496d1dd76"), | ||
// lark.Email("[email protected]"), | ||
// lark.ChatID("oc_a0553eda9014c201e6969b478895c230"), | ||
// ) | ||
func (c *customAppService) AddReceivers(ids ...*receiverID) { | ||
c.receiveIDs = append(c.receiveIDs, ids...) | ||
} | ||
|
||
// Send takes a message subject and a message body and sends them to all | ||
// previously registered recipient IDs. | ||
func (c *customAppService) Send(ctx context.Context, subject, message string) error { | ||
for _, id := range c.receiveIDs { | ||
select { | ||
case <-ctx.Done(): | ||
return ctx.Err() | ||
default: | ||
if err := c.cli.SendTo(subject, message, id.id, string(id.typ)); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// larkClientGoLarkChatBot is a wrapper around go-lark/lark's Bot, to be used | ||
// for sending messages with custom apps. | ||
type larkClientGoLarkChatBot struct { | ||
bot *lark.Bot | ||
} | ||
|
||
// SendTo implements the sendToer interface using a go-lark/lark chat bot. | ||
func (l *larkClientGoLarkChatBot) SendTo(subject, message, receiverID, idType string) error { | ||
content := lark.NewPostBuilder(). | ||
Title(subject). | ||
TextTag(message, 1, false). | ||
Render() | ||
msg := lark.NewMsgBuffer(lark.MsgPost).Post(content) | ||
switch receiverIDType(idType) { | ||
case openID: | ||
msg.BindOpenID(receiverID) | ||
case userID: | ||
msg.BindUserID(receiverID) | ||
case unionID: | ||
msg.BindUnionID(receiverID) | ||
case email: | ||
msg.BindEmail(receiverID) | ||
case chatID: | ||
msg.BindChatID(receiverID) | ||
} | ||
res, err := l.bot.PostMessage(msg.Build()) | ||
if err != nil { | ||
return fmt.Errorf("failed to send message: %w", err) | ||
} | ||
if res.Code != 0 { | ||
return fmt.Errorf("send failed with error code %d, please see https://open.larksuite.com/document/ukTMukTMukTM/ugjM14COyUjL4ITN for details", res.Code) | ||
} | ||
return nil | ||
} |
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,80 @@ | ||
package lark | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestAddReceivers(t *testing.T) { | ||
t.Parallel() | ||
|
||
xs := []*receiverID{ | ||
OpenID("ou_c99c5f35d542efc7ee492afe11af19ef"), | ||
UserID("8335aga2"), | ||
} | ||
svc := NewCustomAppService("", "") | ||
svc.AddReceivers(xs...) | ||
|
||
assert.ElementsMatch(t, svc.receiveIDs, xs) | ||
|
||
// Test if adding more receivers afterwards works. | ||
ys := []*receiverID{ | ||
UnionID("on_cad4860e7af114fb4ff6c5d496d1dd76"), | ||
Email("[email protected]"), | ||
ChatID("oc_a0553eda9014c201e6969b478895c230"), | ||
} | ||
svc.AddReceivers(ys...) | ||
|
||
assert.ElementsMatch(t, svc.receiveIDs, append(xs, ys...)) | ||
} | ||
|
||
func TestSendCustomApp(t *testing.T) { | ||
t.Parallel() | ||
ctx := context.Background() | ||
assert := assert.New(t) | ||
|
||
tests := []*receiverID{ | ||
OpenID("ou_c99c5f35d542efc7ee492afe11af19ef"), | ||
UserID("8335aga2"), | ||
UnionID("on_cad4860e7af114fb4ff6c5d496d1dd76"), | ||
Email("[email protected]"), | ||
ChatID("oc_a0553eda9014c201e6969b478895c230"), | ||
} | ||
|
||
// First, test for when the sender returns an error. | ||
for _, tt := range tests { | ||
mockSendToer := NewSendToer(t) | ||
mockSendToer. | ||
On("SendTo", "subject", "message", tt.id, string(tt.typ)). | ||
Return(errors.New("")) | ||
|
||
svc := NewCustomAppService("", "") | ||
svc.cli = mockSendToer | ||
|
||
svc.AddReceivers(tt) | ||
err := svc.Send(ctx, "subject", "message") | ||
assert.NotNil(err) | ||
|
||
mockSendToer.AssertExpectations(t) | ||
} | ||
|
||
// Then test for when the sender does not return an error. | ||
for _, tt := range tests { | ||
mockSendToer := NewSendToer(t) | ||
mockSendToer. | ||
On("SendTo", "subject", "message", tt.id, string(tt.typ)). | ||
Return(nil) | ||
|
||
svc := NewCustomAppService("", "") | ||
svc.cli = mockSendToer | ||
|
||
svc.AddReceivers(tt) | ||
err := svc.Send(ctx, "subject", "message") | ||
assert.Nil(err) | ||
|
||
mockSendToer.AssertExpectations(t) | ||
} | ||
} |
Oops, something went wrong.