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/readme.md b/readme.md index b2b2eb5..367ff35 100644 --- a/readme.md +++ b/readme.md @@ -107,7 +107,8 @@ API using [openapi-generator](https://openapi-generator.tech/#try) | ✅ | Send Video | POST | /send/video | | ✅ | Send Contact | POST | /send/contact | | ✅ | Send Link | POST | /send/link | -| ✅ | Revoke Messave | POST | /message/:message_id/revoke | +| ✅ | Send Location | POST | /send/location | +| ✅ | Revoke Message | POST | /message/:message_id/revoke | ``` ✅ = Available @@ -116,20 +117,21 @@ API using [openapi-generator](https://openapi-generator.tech/#try) ### App User Interface -1. Homepage ![Homepage](https://i.ibb.co/NNX2wWY/home.png) +1. Homepage ![Homepage](https://i.ibb.co/GMNWqRq/homepage.png) 2. Login ![Login](https://i.ibb.co/jkcB15R/login.png) -3. Send Message ![Send Message](https://i.ibb.co/DrCVXS7/send-message.png) -4. Send Image ![Send Image](https://i.ibb.co/WykfQc8/send-image.png) -5. Send File ![Send File](https://i.ibb.co/wC4SfRp/send-file.png) -6. Send Video ![Send Video](https://i.ibb.co/VDCRH3G/send-video.png) +3. Send Message ![Send Message](https://i.ibb.co/rc3NXMX/send-message.png) +4. Send Image ![Send Image](https://i.ibb.co/BcFL3SD/send-image.png) +5. Send File ![Send File](https://i.ibb.co/f4yxjpp/send-file.png) +6. Send Video ![Send Video](https://i.ibb.co/PrD3P51/send-video.png) 7. Send Contact ![Send Contact](https://i.ibb.co/4810H7N/send-contact.png) -8. Revoke Message ![Revoke Message](https://i.ibb.co/yswhvQY/revoke.png?) -9. User Info ![User Info](https://i.ibb.co/3zjX6Cz/user-info.png) -10. User Avatar ![User Avatar](https://i.ibb.co/cysjmjT/user-avatar.png?) -11. My Privacy ![My Privacy](https://i.ibb.co/Cw1sMQz/my-privacy.png) -12. My Group ![My Group](https://i.ibb.co/B6rW8Sh/list-group.png) -13. Auto Reply ![Auto Reply](https://i.ibb.co/D4rTytX/IMG-20220517-162500.jpg) -14. Basic Auth Prompt ![Basic Auth](https://i.ibb.co/PDjQ92W/Screenshot-2022-11-06-at-14-06-29.png) +8. Send Location ![Send Location](https://i.ibb.co/TWsy09G/send-location.png) +9. Revoke Message ![Revoke Message](https://i.ibb.co/yswhvQY/revoke.png?) +10. User Info ![User Info](https://i.ibb.co/3zjX6Cz/user-info.png) +11. User Avatar ![User Avatar](https://i.ibb.co/cysjmjT/user-avatar.png?) +12. My Privacy ![My Privacy](https://i.ibb.co/Cw1sMQz/my-privacy.png) +13. My Group ![My Group](https://i.ibb.co/B6rW8Sh/list-group.png) +14. Auto Reply ![Auto Reply](https://i.ibb.co/D4rTytX/IMG-20220517-162500.jpg) +15. Basic Auth Prompt ![Basic Auth](https://i.ibb.co/PDjQ92W/Screenshot-2022-11-06-at-14-06-29.png) ### Mac OS NOTE diff --git a/src/config/settings.go b/src/config/settings.go index 5e9989d..094a56f 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -6,7 +6,7 @@ import ( ) var ( - AppVersion = "v4.2.0" + AppVersion = "v4.3.0" AppPort = "3000" AppDebug = false AppOs = fmt.Sprintf("AldinoKemal") 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/pkg/whatsapp/whatsapp.go b/src/pkg/whatsapp/whatsapp.go index f0f2213..ad1f0a8 100644 --- a/src/pkg/whatsapp/whatsapp.go +++ b/src/pkg/whatsapp/whatsapp.go @@ -240,7 +240,7 @@ func handler(rawEvt interface{}) { } case *events.HistorySync: id := atomic.AddInt32(&historySyncID, 1) - fileName := fmt.Sprintf("%s/history-%d-%d.json", config.PathStorages, startupTime, id) + fileName := fmt.Sprintf("%s/history-%d-%s-%d-%s.json", config.PathStorages, startupTime, cli.Store.ID.String(), id, evt.Data.SyncType.String()) file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0600) if err != nil { log.Errorf("Failed to open file to write history sync: %v", err) diff --git a/src/services/app.go b/src/services/app.go index 04fed66..8ec0bd8 100644 --- a/src/services/app.go +++ b/src/services/app.go @@ -12,6 +12,7 @@ import ( "go.mau.fi/whatsmeow/store/sqlstore" "os" "path/filepath" + "strings" "time" ) @@ -86,7 +87,7 @@ func (service serviceApp) Login(_ context.Context) (response domainApp.LoginResp func (service serviceApp) Logout(_ context.Context) (err error) { // delete history - files, err := filepath.Glob("./history-*") + files, err := filepath.Glob(fmt.Sprintf("./%s/history-*", config.PathStorages)) if err != nil { return err } @@ -98,7 +99,7 @@ func (service serviceApp) Logout(_ context.Context) (err error) { } } // delete qr images - qrImages, err := filepath.Glob("./statics/images/qrcode/scan-*") + qrImages, err := filepath.Glob(fmt.Sprintf("./%s/scan-*", config.PathQrCode)) if err != nil { return err } @@ -110,6 +111,21 @@ func (service serviceApp) Logout(_ context.Context) (err error) { } } + // delete senditems + qrItems, err := filepath.Glob(fmt.Sprintf("./%s/*", config.PathSendItems)) + if err != nil { + return err + } + + for _, f := range qrItems { + if !strings.Contains(f, ".gitignore") { + err = os.Remove(f) + if err != nil { + return err + } + } + } + err = service.WaCli.Logout() return } diff --git a/src/services/send.go b/src/services/send.go index e7eb7a2..eae34d4 100644 --- a/src/services/send.go +++ b/src/services/send.go @@ -32,7 +32,7 @@ func NewSendService(waCli *whatsmeow.Client) domainSend.ISendService { } func (service serviceSend) SendText(ctx context.Context, request domainSend.MessageRequest) (response domainSend.MessageResponse, err error) { - err = validations.ValidateSendMessage(request) + err = validations.ValidateSendMessage(ctx, request) if err != nil { return response, err } @@ -54,7 +54,7 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess } func (service serviceSend) SendImage(ctx context.Context, request domainSend.ImageRequest) (response domainSend.ImageResponse, err error) { - err = validations.ValidateSendImage(request) + err = validations.ValidateSendImage(ctx, request) if err != nil { return response, err } @@ -152,7 +152,7 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima } func (service serviceSend) SendFile(ctx context.Context, request domainSend.FileRequest) (response domainSend.FileResponse, err error) { - err = validations.ValidateSendFile(request) + err = validations.ValidateSendFile(ctx, request) if err != nil { return response, err } @@ -207,7 +207,7 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File } func (service serviceSend) SendVideo(ctx context.Context, request domainSend.VideoRequest) (response domainSend.VideoResponse, err error) { - err = validations.ValidateSendVideo(request) + err = validations.ValidateSendVideo(ctx, request) if err != nil { return response, err } @@ -286,20 +286,23 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid msgId := whatsmeow.GenerateMessageID() msg := &waProto.Message{VideoMessage: &waProto.VideoMessage{ - Url: proto.String(uploaded.URL), - Mimetype: proto.String(http.DetectContentType(dataWaVideo)), - Caption: proto.String(request.Caption), - FileLength: proto.Uint64(uploaded.FileLength), - FileSha256: uploaded.FileSHA256, - FileEncSha256: uploaded.FileEncSHA256, - MediaKey: uploaded.MediaKey, - DirectPath: proto.String(uploaded.DirectPath), - ViewOnce: proto.Bool(request.ViewOnce), - JpegThumbnail: dataWaThumbnail, + Url: proto.String(uploaded.URL), + Mimetype: proto.String(http.DetectContentType(dataWaVideo)), + Caption: proto.String(request.Caption), + FileLength: proto.Uint64(uploaded.FileLength), + FileSha256: uploaded.FileSHA256, + FileEncSha256: uploaded.FileEncSHA256, + MediaKey: uploaded.MediaKey, + DirectPath: proto.String(uploaded.DirectPath), + ViewOnce: proto.Bool(request.ViewOnce), + JpegThumbnail: dataWaThumbnail, + ThumbnailEncSha256: dataWaThumbnail, + ThumbnailSha256: dataWaThumbnail, + ThumbnailDirectPath: proto.String(uploaded.DirectPath), }} ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg) go func() { - errDelete := utils.RemoveFile(0, deletedItems...) + errDelete := utils.RemoveFile(1, deletedItems...) if errDelete != nil { fmt.Println(errDelete) } @@ -314,7 +317,7 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid } func (service serviceSend) SendContact(ctx context.Context, request domainSend.ContactRequest) (response domainSend.ContactResponse, err error) { - err = validations.ValidateSendContact(request) + err = validations.ValidateSendContact(ctx, request) if err != nil { return response, err } @@ -341,7 +344,7 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C } func (service serviceSend) SendLink(ctx context.Context, request domainSend.LinkRequest) (response domainSend.LinkResponse, err error) { - err = validations.ValidateSendLink(request) + err = validations.ValidateSendLink(ctx, request) if err != nil { return response, err } @@ -372,8 +375,38 @@ func (service serviceSend) SendLink(ctx context.Context, request domainSend.Link return response, nil } -func (service serviceSend) Revoke(_ context.Context, request domainSend.RevokeRequest) (response domainSend.RevokeResponse, err error) { - err = validations.ValidateRevokeMessage(request) +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(ctx context.Context, request domainSend.RevokeRequest) (response domainSend.RevokeResponse, err error) { + err = validations.ValidateRevokeMessage(ctx, request) if err != nil { return response, err } @@ -394,7 +427,7 @@ func (service serviceSend) Revoke(_ context.Context, request domainSend.RevokeRe } func (service serviceSend) UpdateMessage(ctx context.Context, request domainSend.UpdateMessageRequest) (response domainSend.UpdateMessageResponse, err error) { - err = validations.ValidateUpdateMessage(request) + err = validations.ValidateUpdateMessage(ctx, request) if err != nil { return response, err } diff --git a/src/services/user.go b/src/services/user.go index 16a825b..ba24d7b 100644 --- a/src/services/user.go +++ b/src/services/user.go @@ -21,8 +21,8 @@ func NewUserService(waCli *whatsmeow.Client) domainUser.IUserService { } } -func (service userService) Info(_ context.Context, request domainUser.InfoRequest) (response domainUser.InfoResponse, err error) { - err = validations.ValidateUserInfo(request) +func (service userService) Info(ctx context.Context, request domainUser.InfoRequest) (response domainUser.InfoResponse, err error) { + err = validations.ValidateUserInfo(ctx, request) if err != nil { return response, err } @@ -64,8 +64,8 @@ func (service userService) Info(_ context.Context, request domainUser.InfoReques return response, nil } -func (service userService) Avatar(_ context.Context, request domainUser.AvatarRequest) (response domainUser.AvatarResponse, err error) { - err = validations.ValidateUserAvatar(request) +func (service userService) Avatar(ctx context.Context, request domainUser.AvatarRequest) (response domainUser.AvatarResponse, err error) { + err = validations.ValidateUserAvatar(ctx, request) if err != nil { return response, err } @@ -106,7 +106,7 @@ func (service userService) MyListGroups(_ context.Context) (response domainUser. func (service userService) MyPrivacySetting(_ context.Context) (response domainUser.MyPrivacySettingResponse, err error) { whatsapp.MustLogin(service.WaCli) - resp, err := service.WaCli.TryFetchPrivacySettings(false) + resp, err := service.WaCli.TryFetchPrivacySettings(true) if err != nil { return } diff --git a/src/validations/send_validation.go b/src/validations/send_validation.go index 101f922..5078201 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" @@ -10,8 +11,8 @@ import ( "github.com/go-ozzo/ozzo-validation/v4/is" ) -func ValidateSendMessage(request domainSend.MessageRequest) error { - err := validation.ValidateStruct(&request, +func ValidateSendMessage(ctx context.Context, request domainSend.MessageRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), validation.Field(&request.Message, validation.Required), ) @@ -22,8 +23,8 @@ func ValidateSendMessage(request domainSend.MessageRequest) error { return nil } -func ValidateSendImage(request domainSend.ImageRequest) error { - err := validation.ValidateStruct(&request, +func ValidateSendImage(ctx context.Context, request domainSend.ImageRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), validation.Field(&request.Image, validation.Required), ) @@ -45,8 +46,8 @@ func ValidateSendImage(request domainSend.ImageRequest) error { return nil } -func ValidateSendFile(request domainSend.FileRequest) error { - err := validation.ValidateStruct(&request, +func ValidateSendFile(ctx context.Context, request domainSend.FileRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), validation.Field(&request.File, validation.Required), ) @@ -63,8 +64,8 @@ func ValidateSendFile(request domainSend.FileRequest) error { return nil } -func ValidateSendVideo(request domainSend.VideoRequest) error { - err := validation.ValidateStruct(&request, +func ValidateSendVideo(ctx context.Context, request domainSend.VideoRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), validation.Field(&request.Video, validation.Required), ) @@ -91,8 +92,8 @@ func ValidateSendVideo(request domainSend.VideoRequest) error { return nil } -func ValidateSendContact(request domainSend.ContactRequest) error { - err := validation.ValidateStruct(&request, +func ValidateSendContact(ctx context.Context, request domainSend.ContactRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), validation.Field(&request.ContactPhone, validation.Required), validation.Field(&request.ContactName, validation.Required), @@ -105,8 +106,8 @@ func ValidateSendContact(request domainSend.ContactRequest) error { return nil } -func ValidateSendLink(request domainSend.LinkRequest) error { - err := validation.ValidateStruct(&request, +func ValidateSendLink(ctx context.Context, request domainSend.LinkRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), validation.Field(&request.Link, validation.Required, is.URL), validation.Field(&request.Caption, validation.Required), @@ -119,8 +120,22 @@ func ValidateSendLink(request domainSend.LinkRequest) error { return nil } -func ValidateRevokeMessage(request domainSend.RevokeRequest) error { - err := validation.ValidateStruct(&request, +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(ctx context.Context, request domainSend.RevokeRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), validation.Field(&request.MessageID, validation.Required), ) @@ -132,8 +147,8 @@ func ValidateRevokeMessage(request domainSend.RevokeRequest) error { return nil } -func ValidateUpdateMessage(request domainSend.UpdateMessageRequest) error { - err := validation.ValidateStruct(&request, +func ValidateUpdateMessage(ctx context.Context, request domainSend.UpdateMessageRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), validation.Field(&request.MessageID, validation.Required), validation.Field(&request.Message, validation.Required), diff --git a/src/validations/send_validation_test.go b/src/validations/send_validation_test.go index f04dedf..27359a5 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" @@ -45,7 +46,7 @@ func TestValidateSendMessage(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateSendMessage(tt.args.request) + err := ValidateSendMessage(context.Background(), tt.args.request) assert.Equal(t, tt.err, err) }) } @@ -107,7 +108,7 @@ func TestValidateSendImage(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateSendImage(tt.args.request) + err := ValidateSendImage(context.Background(), tt.args.request) assert.Equal(t, tt.err, err) }) } @@ -156,7 +157,7 @@ func TestValidateSendFile(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateSendFile(tt.args.request) + err := ValidateSendFile(context.Background(), tt.args.request) assert.Equal(t, tt.err, err) }) } @@ -231,7 +232,7 @@ func TestValidateSendVideo(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateSendVideo(tt.args.request) + err := ValidateSendVideo(context.Background(), tt.args.request) assert.Equal(t, tt.err, err) }) } @@ -295,7 +296,7 @@ func TestValidateSendLink(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateSendLink(tt.args.request) + err := ValidateSendLink(context.Background(), tt.args.request) assert.Equal(t, tt.err, err) }) } @@ -338,7 +339,7 @@ func TestValidateRevokeMessage(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateRevokeMessage(tt.args.request) + err := ValidateRevokeMessage(context.Background(), tt.args.request) assert.Equal(t, tt.err, err) }) } @@ -393,7 +394,7 @@ func TestValidateUpdateMessage(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateUpdateMessage(tt.args.request) + err := ValidateUpdateMessage(context.Background(), tt.args.request) assert.Equal(t, tt.err, err) }) } @@ -448,7 +449,80 @@ func TestValidateSendContact(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateSendContact(tt.args.request) + err := ValidateSendContact(context.Background(), tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} + +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) }) } diff --git a/src/validations/user_validation.go b/src/validations/user_validation.go index 8f1b1d8..36a27fe 100644 --- a/src/validations/user_validation.go +++ b/src/validations/user_validation.go @@ -1,13 +1,14 @@ package validations import ( + "context" domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" validation "github.com/go-ozzo/ozzo-validation/v4" ) -func ValidateUserInfo(request domainUser.InfoRequest) error { - err := validation.ValidateStruct(&request, +func ValidateUserInfo(ctx context.Context, request domainUser.InfoRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), ) @@ -17,8 +18,8 @@ func ValidateUserInfo(request domainUser.InfoRequest) error { return nil } -func ValidateUserAvatar(request domainUser.AvatarRequest) error { - err := validation.ValidateStruct(&request, +func ValidateUserAvatar(ctx context.Context, request domainUser.AvatarRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), ) diff --git a/src/validations/user_validation_test.go b/src/validations/user_validation_test.go index d444887..15f7751 100644 --- a/src/validations/user_validation_test.go +++ b/src/validations/user_validation_test.go @@ -1,6 +1,7 @@ package validations import ( + "context" domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" "github.com/stretchr/testify/assert" @@ -34,7 +35,7 @@ func TestValidateUserAvatar(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateUserAvatar(tt.args.request) + err := ValidateUserAvatar(context.Background(), tt.args.request) assert.Equal(t, tt.err, err) }) } @@ -67,7 +68,7 @@ func TestValidateUserInfo(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateUserInfo(tt.args.request) + err := ValidateUserInfo(context.Background(), tt.args.request) assert.Equal(t, tt.err, err) }) } diff --git a/src/views/index.html b/src/views/index.html index b437a5b..28f3d16 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -124,6 +124,15 @@ +
+
+ Send +
Send Location
+
+ Send location to any whatsapp number +
+
+
Revoke @@ -274,7 +283,7 @@
- +
@@ -322,7 +331,7 @@
+ aria-label="caption">
@@ -379,7 +388,7 @@
- +
@@ -426,12 +435,12 @@
+ aria-label="contact name">
+ aria-label="contact phone">
@@ -444,6 +453,48 @@
+ + + @@ -1112,6 +1163,69 @@ } } + const sendLocation = { + data() { + return { + location_type: 'user', + location_phone: '', + location_latitude: '', + location_longitude: '', + location_loading: false, + } + }, + computed: { + location_phone_id() { + return this.location_type === 'user' ? `${this.location_phone}@${this.type_user}` : `${this.location_phone}@${this.type_group}` + } + }, + methods: { + sendLocationModal() { + $('#modalSendLocation').modal({ + onApprove: function () { + return false; + } + }).modal('show'); + }, + async sendLocationProcess() { + try { + let response = await this.sendLocationApi() + showSuccessInfo(response) + $('#modalSendLocation').modal('hide'); + } catch (err) { + showErrorInfo(err) + } + }, + sendLocationApi() { + return new Promise(async (resolve, reject) => { + try { + this.location_loading = true; + let payload = new FormData(); + payload.append("phone", this.location_phone_id) + payload.append("latitude", this.location_latitude) + payload.append("longitude", this.location_longitude) + let response = await http.post(`/send/location`, payload) + this.sendLocationReset(); + resolve(response.data.message) + } catch (error) { + if (error.response) { + reject(error.response.data.message) + } else { + reject(error.message) + } + } finally { + this.location_loading = false; + } + }) + }, + sendLocationReset() { + this.location_phone = ''; + this.location_latitude = ''; + this.location_longitude = ''; + this.location_type = 'user'; + }, + } + } + const userGroups = { data() { return { @@ -1352,7 +1466,11 @@ return tanggal.format('LLL'); } }, - mixins: [login, logout, reconnect, sendMessage, sendImage, sendFile, sendVideo, sendContact, sendRevoke, userGroups, userPrivacy, userAvatar, userInfo] + mixins: [ + login, logout, reconnect, + sendMessage, sendImage, sendFile, sendVideo, sendContact, sendRevoke, sendLocation, + userGroups, userPrivacy, userAvatar, userInfo + ] }).mount('#app')