From 84a7df28c6aa6ce30c1db8b2a2a8633a93447905 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sat, 3 Dec 2022 22:57:50 +0700 Subject: [PATCH] feat: add send location --- docs/openapi.yaml | 45 ++++++++++++++- src/domains/send/location.go | 12 ++++ src/domains/send/send.go | 1 + src/internal/rest/send.go | 18 ++++++ src/pkg/utils/general.go | 10 ++++ src/services/send.go | 30 ++++++++++ src/validations/send_validation.go | 15 +++++ src/validations/send_validation_test.go | 74 +++++++++++++++++++++++++ 8 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/domains/send/location.go diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 2b61892..f83081a 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: WhatsApp API MultiDevice - version: 3.0.0 + version: 3.1.0 description: This API is used for sending whatsapp via API servers: - url: http://localhost:3000 @@ -566,6 +566,49 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorInternalServer' + /send/location: + post: + operationId: sendLocation + tags: + - message + summary: Send Location + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + phone: + type: integer + example: '6289685024051@s.whatsapp.net' + description: Phone number with country code + latitude: + type: string + example: "-7.797068" + description: Latitude coordinate + longitude: + type: string + example: '110.370529' + description: Longitude coordinate + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SendResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorBadRequest' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorInternalServer' /message/:message_id/revoke: post: operationId: revokeMessage diff --git a/src/domains/send/location.go b/src/domains/send/location.go new file mode 100644 index 0000000..a577f4d --- /dev/null +++ b/src/domains/send/location.go @@ -0,0 +1,12 @@ +package send + +type LocationRequest struct { + Phone string `json:"phone" form:"phone"` + Latitude string `json:"latitude" form:"latitude"` + Longitude string `json:"longitude" form:"longitude"` +} + +type LocationResponse struct { + MessageID string `json:"message_id"` + Status string `json:"status"` +} diff --git a/src/domains/send/send.go b/src/domains/send/send.go index c6fe122..ddcc0b0 100644 --- a/src/domains/send/send.go +++ b/src/domains/send/send.go @@ -11,6 +11,7 @@ type ISendService interface { SendVideo(ctx context.Context, request VideoRequest) (response VideoResponse, err error) SendContact(ctx context.Context, request ContactRequest) (response ContactResponse, err error) SendLink(ctx context.Context, request LinkRequest) (response LinkResponse, err error) + SendLocation(ctx context.Context, request LocationRequest) (response LocationResponse, err error) Revoke(ctx context.Context, request RevokeRequest) (response RevokeResponse, err error) UpdateMessage(ctx context.Context, request UpdateMessageRequest) (response UpdateMessageResponse, err error) } diff --git a/src/internal/rest/send.go b/src/internal/rest/send.go index 5de40e7..7872427 100644 --- a/src/internal/rest/send.go +++ b/src/internal/rest/send.go @@ -19,6 +19,7 @@ func InitRestSend(app *fiber.App, service domainSend.ISendService) Send { app.Post("/send/video", rest.SendVideo) app.Post("/send/contact", rest.SendContact) app.Post("/send/link", rest.SendLink) + app.Post("/send/location", rest.SendLocation) app.Post("/message/:message_id/revoke", rest.RevokeMessage) app.Post("/message/:message_id/update", rest.UpdateMessage) return rest @@ -140,6 +141,23 @@ func (controller *Send) SendLink(c *fiber.Ctx) error { }) } +func (controller *Send) SendLocation(c *fiber.Ctx) error { + var request domainSend.LocationRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + whatsapp.SanitizePhone(&request.Phone) + + response, err := controller.Service.SendLocation(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Message: response.Status, + Results: response, + }) +} + func (controller *Send) RevokeMessage(c *fiber.Ctx) error { var request domainSend.RevokeRequest err := c.BodyParser(&request) diff --git a/src/pkg/utils/general.go b/src/pkg/utils/general.go index 133ac22..a1a96ac 100644 --- a/src/pkg/utils/general.go +++ b/src/pkg/utils/general.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "path/filepath" + "strconv" + "strings" "time" ) @@ -46,3 +48,11 @@ func PanicIfNeeded(err any, message ...string) { } } } + +func StrToFloat64(text string) float64 { + var result float64 + if text != "" { + result, _ = strconv.ParseFloat(strings.TrimSpace(text), 64) + } + return result +} diff --git a/src/services/send.go b/src/services/send.go index e7eb7a2..9aad1a9 100644 --- a/src/services/send.go +++ b/src/services/send.go @@ -372,6 +372,36 @@ func (service serviceSend) SendLink(ctx context.Context, request domainSend.Link return response, nil } +func (service serviceSend) SendLocation(ctx context.Context, request domainSend.LocationRequest) (response domainSend.LocationResponse, err error) { + err = validations.ValidateSendLocation(ctx, request) + if err != nil { + return response, err + } + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err + } + + // Compose WhatsApp Proto + msgId := whatsmeow.GenerateMessageID() + msg := &waProto.Message{ + LocationMessage: &waProto.LocationMessage{ + DegreesLatitude: proto.Float64(utils.StrToFloat64(request.Latitude)), + DegreesLongitude: proto.Float64(utils.StrToFloat64(request.Longitude)), + }, + } + + // Send WhatsApp Message Proto + ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg) + if err != nil { + return response, err + } + + response.MessageID = msgId + response.Status = fmt.Sprintf("Send location success %s (server timestamp: %s)", request.Phone, ts) + return response, nil +} + func (service serviceSend) Revoke(_ context.Context, request domainSend.RevokeRequest) (response domainSend.RevokeResponse, err error) { err = validations.ValidateRevokeMessage(request) if err != nil { diff --git a/src/validations/send_validation.go b/src/validations/send_validation.go index 101f922..3b77bf6 100644 --- a/src/validations/send_validation.go +++ b/src/validations/send_validation.go @@ -1,6 +1,7 @@ package validations import ( + "context" "fmt" "github.com/aldinokemal/go-whatsapp-web-multidevice/config" domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" @@ -119,6 +120,20 @@ func ValidateSendLink(request domainSend.LinkRequest) error { return nil } +func ValidateSendLocation(ctx context.Context, request domainSend.LocationRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, + validation.Field(&request.Phone, validation.Required), + validation.Field(&request.Latitude, validation.Required, is.Latitude), + validation.Field(&request.Longitude, validation.Required, is.Longitude), + ) + + if err != nil { + return pkgError.ValidationError(err.Error()) + } + + return nil +} + func ValidateRevokeMessage(request domainSend.RevokeRequest) error { err := validation.ValidateStruct(&request, validation.Field(&request.Phone, validation.Required), diff --git a/src/validations/send_validation_test.go b/src/validations/send_validation_test.go index f04dedf..4e9fa22 100644 --- a/src/validations/send_validation_test.go +++ b/src/validations/send_validation_test.go @@ -1,6 +1,7 @@ package validations import ( + "context" domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" "github.com/stretchr/testify/assert" @@ -453,3 +454,76 @@ func TestValidateSendContact(t *testing.T) { }) } } + +func TestValidateSendLocation(t *testing.T) { + type args struct { + request domainSend.LocationRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success normal condition", + args: args{request: domainSend.LocationRequest{ + Phone: "1728937129312@s.whatsapp.net", + Latitude: "-7.797068", + Longitude: "110.370529", + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainSend.LocationRequest{ + Phone: "", + Latitude: "-7.797068", + Longitude: "110.370529", + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + { + name: "should error with empty latitude", + args: args{request: domainSend.LocationRequest{ + Phone: "1728937129312@s.whatsapp.net", + Latitude: "", + Longitude: "110.370529", + }}, + err: pkgError.ValidationError("latitude: cannot be blank."), + }, + { + name: "should error with empty longitude", + args: args{request: domainSend.LocationRequest{ + Phone: "1728937129312@s.whatsapp.net", + Latitude: "-7.797068", + Longitude: "", + }}, + err: pkgError.ValidationError("longitude: cannot be blank."), + }, + { + name: "should error with invalid latitude", + args: args{request: domainSend.LocationRequest{ + Phone: "1728937129312@s.whatsapp.net", + Latitude: "ABCDEF", + Longitude: "110.370529", + }}, + err: pkgError.ValidationError("latitude: must be a valid latitude."), + }, + { + name: "should error with invalid latitude", + args: args{request: domainSend.LocationRequest{ + Phone: "1728937129312@s.whatsapp.net", + Latitude: "-7.797068", + Longitude: "ABCDEF", + }}, + err: pkgError.ValidationError("longitude: must be a valid longitude."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateSendLocation(context.Background(), tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +}