From 8aa5c9d2b60f7a879b3691b68220b34d78dd34c0 Mon Sep 17 00:00:00 2001 From: Irfan Sofyana Putra <33191443+irfansofyana@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:45:06 +0700 Subject: [PATCH] feat(service): Add Viber (#415) --- README.md | 1 + go.mod | 3 + go.sum | 8 +++ service/matrix/mock_matrix_client.go | 6 +- service/viber/README | 75 +++++++++++++++++++ service/viber/doc.go | 39 ++++++++++ service/viber/mock_viber_client.go | 70 ++++++++++++++++++ service/viber/viber.go | 61 ++++++++++++++++ service/viber/viber_test.go | 103 +++++++++++++++++++++++++++ 9 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 service/viber/README create mode 100644 service/viber/doc.go create mode 100644 service/viber/mock_viber_client.go create mode 100644 service/viber/viber.go create mode 100644 service/viber/viber_test.go diff --git a/README.md b/README.md index 08cc54d3..689c6256 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ Yes, please! Contributions of all kinds are very welcome! Feel free to check our | [TextMagic](https://www.textmagic.com) | [service/textmagic](service/textmagic) | [textmagic/textmagic-rest-go-v2](https://github.com/textmagic/textmagic-rest-go-v2) | :heavy_check_mark: | | [Twilio](https://www.twilio.com/) | [service/twilio](service/twilio) | [kevinburke/twilio-go](https://github.com/kevinburke/twilio-go) | :heavy_check_mark: | | [Twitter](https://twitter.com) | [service/twitter](service/twitter) | [dghubble/go-twitter](https://github.com/dghubble/go-twitter) | :heavy_check_mark: | +| [Viber](https://www.viber.com) | [service/viber](service/viber) | [mileusna/viber](https://github.com/mileusna/viber) | :heavy_check_mark: | | [WeChat](https://www.wechat.com) | [service/wechat](service/wechat) | [silenceper/wechat](https://github.com/silenceper/wechat) | :heavy_check_mark: | | [WhatsApp](https://www.whatsapp.com) | [service/whatsapp](service/whatsapp) | [Rhymen/go-whatsapp](https://github.com/Rhymen/go-whatsapp) | :x: | diff --git a/go.mod b/go.mod index d6f4ac62..5b3d4121 100644 --- a/go.mod +++ b/go.mod @@ -67,6 +67,9 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/go-types v0.0.0-20210723172823-2deba1f80ba7 // indirect github.com/kevinburke/rest v0.0.0-20210506044642-5611499aa33c // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mileusna/viber v1.0.1 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index b4b54c8a..d9162d94 100644 --- a/go.sum +++ b/go.sum @@ -149,6 +149,14 @@ github.com/line/line-bot-sdk-go v7.8.0+incompatible h1:Uf9/OxV0zCVfqyvwZPH8CrdiH github.com/line/line-bot-sdk-go v7.8.0+incompatible/go.mod h1:0RjLjJEAU/3GIcHkC3av6O4jInAbt25nnZVmOFUgDBg= github.com/mailgun/mailgun-go/v4 v4.8.1 h1:1+MdKakJuXnW2JJDbyPdO1ngAANOyHyVPxQvFF8Sq6c= github.com/mailgun/mailgun-go/v4 v4.8.1/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mileusna/viber v1.0.1 h1:gWB6/lKoWYVxkH0Jb8jRnGIRZ/9DEM7RBZRJHRfdYWs= +github.com/mileusna/viber v1.0.1/go.mod h1:Pxu/iPMnYjnHgu+bEp3SiKWHWmlf/kDp/yOX8XUdYrQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/service/matrix/mock_matrix_client.go b/service/matrix/mock_matrix_client.go index fd8c6a71..44473caa 100644 --- a/service/matrix/mock_matrix_client.go +++ b/service/matrix/mock_matrix_client.go @@ -3,12 +3,10 @@ package matrix import ( + mock "github.com/stretchr/testify/mock" + mautrix "maunium.net/go/mautrix" event "maunium.net/go/mautrix/event" id "maunium.net/go/mautrix/id" - - mautrix "maunium.net/go/mautrix" - - mock "github.com/stretchr/testify/mock" ) // mockMatrixClient is an autogenerated mock type for the matrixClient type diff --git a/service/viber/README b/service/viber/README new file mode 100644 index 00000000..b3588b29 --- /dev/null +++ b/service/viber/README @@ -0,0 +1,75 @@ +# Viber + +## Prerequisites + +### Create a Viber Bot + +In order to use the Viber notification service, we'll need to create a new Viber bot [here](https://partners.viber.com/account/create-bot-account). + +### Setting the webhook + +After we have done with the bot setup, we'll need to have a webhook that will be used to receive callbacks from the Viber server otherwise we will not be able to send a message. + +Please note that your webhook needs to be valid otherwise it will not work properly. You can read more details about the Viber webhook [here](https://developers.viber.com/docs/api/rest-bot-api/#webhooks) and about the callback [here](https://developers.viber.com/docs/api/rest-bot-api/#callbacks). + +#### Tips: Easy setup for webhook + +If you need to set up webhook easily like for example only for local testing, you can utilize [Google App Scripts](https://www.google.com/script/start/) and create a simple Web app from it. Here is the example script: + +```javascript +function doPost(e) { +  const contents = JSON.parse(e.postData.contents) +  Logger.log(JSON.stringify(contents)) +} +``` + +_In short, it will just receive the POST request, and log the content_. + +Don't forget to deploy the script as a web app and share the access with anyone. + +You'll get a URL like https://script.google.com/macros/s/xxx/exec and this URL will be your webhook URL. + +## Usage + +Here is an example use case on how you can use Viber: + +```go +package main + +import ( + "context" + "log" + + "github.com/nikoksr/notify" + "github.com/nikoksr/notify/service/viber" +) + +const appKey = "your-viber-token" +const webhookURL = "https://webhook.com" +const senderName = "vibersofyana" + +func main() { + viberSvc := viber.New(appKey, senderName, "") + + err := viberSvc.SetWebhook(webhookURL) // this only needs to be called once + if err != nil { + log.Fatalf("set webhook to viber server failed: %v", err) + } + + viberSvc.AddReceivers("receiver-viber-user-id") // can add as many as required + notifier := notify.New() + + notifier.UseServices(viberSvc) + if err := notifier.Send(context.Background(), "TEST", "Message using golang notifier library"); err != nil { + log.Fatalf("notifier.Send() failed: %s", err.Error()) + } + + log.Println("Notification sent") +} +``` + +> ❗️**Viber is only allowing the bot to send the message to their subscriber**. Therefore, in order to send the notification, we need to make sure that the receiver already subscribed to the bot. Read more details here: https://developers.viber.com/docs/api/rest-bot-api/#send-message + +## Attachment + +- [Viber API Documentation](https://developers.viber.com/docs/) diff --git a/service/viber/doc.go b/service/viber/doc.go new file mode 100644 index 00000000..cb430a0a --- /dev/null +++ b/service/viber/doc.go @@ -0,0 +1,39 @@ +/* +Package viber provides a service for sending messages to viber. + +Usage: + + package main + + import ( + "context" + "log" + + "github.com/nikoksr/notify" + "github.com/nikoksr/notify/service/viber" + ) + + const appKey = "your-viber-token" + const webhookURL = "https://webhook.com" + const senderName = "vibersofyana" + + func main() { + viberSvc := viber.New(appKey, senderName, "") + + err := viberSvc.SetWebhook(webhookURL) // this only needs to be called once + if err != nil { + log.Fatalf("set webhook to viber server failed: %v", err) + } + + viberSvc.AddReceivers("receiver-viber-user-id") // can add as many as required + notifier := notify.New() + + notifier.UseServices(viberSvc) + if err := notifier.Send(context.Background(), "TEST", "Message using golang notifier library"); err != nil { + log.Fatalf("notifier.Send() failed: %s", err.Error()) + } + + log.Println("Notification sent") + } +*/ +package viber diff --git a/service/viber/mock_viber_client.go b/service/viber/mock_viber_client.go new file mode 100644 index 00000000..d34249ca --- /dev/null +++ b/service/viber/mock_viber_client.go @@ -0,0 +1,70 @@ +// Code generated by mockery v2.14.0. DO NOT EDIT. + +package viber + +import ( + mileusnaviber "github.com/mileusna/viber" + mock "github.com/stretchr/testify/mock" +) + +// mockViberClient is an autogenerated mock type for the viberClient type +type mockViberClient struct { + mock.Mock +} + +// SendTextMessage provides a mock function with given fields: receiver, msg +func (_m *mockViberClient) SendTextMessage(receiver string, msg string) (uint64, error) { + ret := _m.Called(receiver, msg) + + var r0 uint64 + if rf, ok := ret.Get(0).(func(string, string) uint64); ok { + r0 = rf(receiver, msg) + } else { + r0 = ret.Get(0).(uint64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(receiver, msg) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetWebhook provides a mock function with given fields: url, eventTypes +func (_m *mockViberClient) SetWebhook(url string, eventTypes []string) (mileusnaviber.WebhookResp, error) { + ret := _m.Called(url, eventTypes) + + var r0 mileusnaviber.WebhookResp + if rf, ok := ret.Get(0).(func(string, []string) mileusnaviber.WebhookResp); ok { + r0 = rf(url, eventTypes) + } else { + r0 = ret.Get(0).(mileusnaviber.WebhookResp) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, []string) error); ok { + r1 = rf(url, eventTypes) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTnewMockViberClient interface { + mock.TestingT + Cleanup(func()) +} + +// newMockViberClient creates a new instance of mockViberClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func newMockViberClient(t mockConstructorTestingTnewMockViberClient) *mockViberClient { + mock := &mockViberClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/service/viber/viber.go b/service/viber/viber.go new file mode 100644 index 00000000..61ac9e26 --- /dev/null +++ b/service/viber/viber.go @@ -0,0 +1,61 @@ +package viber + +import ( + "context" + + vb "github.com/mileusna/viber" + "github.com/pkg/errors" +) + +//go:generate mockery --name=viberClient --output=. --case=underscore --inpackage +type viberClient interface { + SetWebhook(url string, eventTypes []string) (vb.WebhookResp, error) + SendTextMessage(receiver, msg string) (uint64, error) +} + +// Compile-time check to ensure that vb.Viber implements the viberClient interface. +var _ viberClient = new(vb.Viber) + +// Viber struct holds necessary fields to communicate with Viber API +type Viber struct { + Client viberClient + SubscribedUserIDs []string +} + +// New returns a new instance of Viber notification service +func New(appKey, senderName, senderAvatar string) *Viber { + return &Viber{ + Client: vb.New(appKey, senderName, senderAvatar), + SubscribedUserIDs: []string{}, + } +} + +// AddReceivers receives subscribed user IDs then add them to internal receivers list +func (v *Viber) AddReceivers(subscribedUserIDs ...string) { + v.SubscribedUserIDs = append(v.SubscribedUserIDs, subscribedUserIDs...) +} + +// SetWebhook receives a URL that will we used as a webhook URL for Viber +func (v *Viber) SetWebhook(webhookURL string) error { + _, err := v.Client.SetWebhook(webhookURL, []string{}) + return err +} + +// Send takes a message subject and a message body and sends them to all previously set userIds +func (v *Viber) Send(ctx context.Context, subject, message string) error { + fullMessage := subject + "\n" + message // Treating subject as message title + + for _, subscribedUserID := range v.SubscribedUserIDs { + select { + case <-ctx.Done(): + return ctx.Err() + default: + _, err := v.Client.SendTextMessage(subscribedUserID, fullMessage) + if err != nil { + return errors.Wrapf(err, "failed to send message to User ID '%s'", subscribedUserID) + } + } + } + + return nil +} diff --git a/service/viber/viber_test.go b/service/viber/viber_test.go new file mode 100644 index 00000000..03fc1acb --- /dev/null +++ b/service/viber/viber_test.go @@ -0,0 +1,103 @@ +package viber + +import ( + "context" + "errors" + "testing" + + vb "github.com/mileusna/viber" + "github.com/stretchr/testify/require" +) + +func TestViber_New(t *testing.T) { + t.Parallel() + + assert := require.New(t) + + viber := New("appkey", "senderName", "senderAvatar") + assert.NotNil(viber) +} + +func TestViber_AddReceivers(t *testing.T) { + t.Parallel() + + assert := require.New(t) + + viber := New("appkey", "senderName", "senderAvatar") + assert.Len(viber.SubscribedUserIDs, 0) + + viber.AddReceivers("first-subscriber") + assert.Len(viber.SubscribedUserIDs, 1) + + viber.AddReceivers("second-subscriber", "third-subscriber") + assert.Len(viber.SubscribedUserIDs, 3) +} + +func TestViber_SetWebhook(t *testing.T) { + t.Parallel() + assert := require.New(t) + viber := New("appkey", "senderName", "senderAvatar") + assert.NotNil(viber) + + // Test error + viberMock := newMockViberClient(t) + webhookURLMock := "https://example-webhook.com" + viberMock. + On("SetWebhook", webhookURLMock, []string{}). + Return(vb.WebhookResp{}, errors.New("set webhook error")) + + viber.Client = viberMock + err := viber.SetWebhook(webhookURLMock) + assert.NotNil(err) + viberMock.AssertExpectations(t) + + // Test success + viberMock = newMockViberClient(t) + viberMock. + On("SetWebhook", webhookURLMock, []string{}). + Return(vb.WebhookResp{}, nil) + + viber.Client = viberMock + err = viber.SetWebhook(webhookURLMock) + assert.Nil(err) + viberMock.AssertExpectations(t) +} + +func TestViber_Send(t *testing.T) { + t.Parallel() + assert := require.New(t) + viber := New("appkey", "senderName", "senderAvatar") + assert.NotNil(viber) + + // No receivers added + ctx := context.Background() + err := viber.Send(ctx, "subject", "message") + assert.Nil(err) + + // Test error response + viberMock := newMockViberClient(t) + viberMock. + On("SendTextMessage", "receiver1", "subject\nmessage"). + Return(uint64(0), errors.New("error send text message")) + + viber.Client = viberMock + viber.AddReceivers("receiver1") + err = viber.Send(ctx, "subject", "message") + assert.NotNil(err) + viberMock.AssertExpectations(t) + + // Test success response + viberMock = newMockViberClient(t) + viberMock. + On("SendTextMessage", "receiver1", "subject\nmessage"). + Return(uint64(0), nil) + viberMock. + On("SendTextMessage", "receiver2", "subject\nmessage"). + Return(uint64(0), nil) + + viber.Client = viberMock + viber.AddReceivers("receiver2") + err = viber.Send(ctx, "subject", "message") + assert.Nil(err) + viberMock.AssertExpectations(t) +}