Browse Source

feat: add send location (#43)

* feat: add send location

* feat: update frontend

* feat: update docs

* feat: update docs

* fix: logout remove file

* fix: remove gitignore

* fix: send video

* feat: add context in validation
pull/45/head v4.3.0
Aldino Kemal 3 years ago
committed by GitHub
parent
commit
38ef3ec468
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      docs/openapi.yaml
  2. 28
      readme.md
  3. 2
      src/config/settings.go
  4. 12
      src/domains/send/location.go
  5. 1
      src/domains/send/send.go
  6. 18
      src/internal/rest/send.go
  7. 10
      src/pkg/utils/general.go
  8. 2
      src/pkg/whatsapp/whatsapp.go
  9. 20
      src/services/app.go
  10. 73
      src/services/send.go
  11. 10
      src/services/user.go
  12. 47
      src/validations/send_validation.go
  13. 90
      src/validations/send_validation_test.go
  14. 9
      src/validations/user_validation.go
  15. 5
      src/validations/user_validation_test.go
  16. 132
      src/views/index.html

45
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

28
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

2
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")

12
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"`
}

1
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)
}

18
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)

10
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
}

2
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)

20
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
}

73
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
}

10
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
}

47
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),

90
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)
})
}

9
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),
)

5
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)
})
}

132
src/views/index.html

@ -124,6 +124,15 @@
</div>
</div>
</div>
<div class="blue card" @click="sendLocationModal()" style="cursor: pointer">
<div class="content">
<a class="ui blue right ribbon label">Send</a>
<div class="header">Send Location</div>
<div class="description">
Send location to any whatsapp number
</div>
</div>
</div>
<div class="red card" @click="sendRevokeModal()" style="cursor: pointer">
<div class="content">
<a class="ui red right ribbon label">Revoke</a>
@ -274,7 +283,7 @@
<div class="field">
<label>Compress</label>
<div class="ui toggle checkbox">
<input type="checkbox" aria-label="view once" v-model="image_compress">
<input type="checkbox" aria-label="compress" v-model="image_compress">
<label>Check for compressing image to smaller size</label>
</div>
</div>
@ -322,7 +331,7 @@
<div class="field">
<label>Caption</label>
<textarea v-model="file_caption" type="text" placeholder="Type some caption (optional)..."
aria-label="caption"></textarea>
aria-label="caption"></textarea>
</div>
<div class="field" style="padding-bottom: 30px">
<label>File</label>
@ -379,7 +388,7 @@
<div class="field">
<label>Compress</label>
<div class="ui toggle checkbox">
<input type="checkbox" aria-label="view once" v-model="video_compress">
<input type="checkbox" aria-label="compress" v-model="video_compress">
<label>Check for compressing image to smaller size</label>
</div>
</div>
@ -426,12 +435,12 @@
<div class="field">
<label>Contact Name</label>
<input v-model="contact_card_name" type="text" placeholder="Please enter contact name"
aria-label="card_name">
aria-label="contact name">
</div>
<div class="field">
<label>Contact Phone</label>
<input v-model="contact_card_phone" type="text" placeholder="Please enter contact phone"
aria-label="card_phone">
aria-label="contact phone">
</div>
</form>
</div>
@ -444,6 +453,48 @@
</div>
</div>
<!-- Modal SendLocation -->
<div class="ui small modal" id="modalSendLocation">
<i class="close icon"></i>
<div class="header">
Send Location
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="location_type" v-model="location_type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="location_phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="location_phone_id" disabled aria-label="whatsapp_id">
</div>
<div class="field">
<label>Location Latitude</label>
<input v-model="location_latitude" type="text" placeholder="Please enter latitude"
aria-label="latitude">
</div>
<div class="field">
<label>Location Longitude</label>
<input v-model="location_longitude" type="text" placeholder="Please enter longitude"
aria-label="longitude">
</div>
</form>
</div>
<div class="actions">
<div class="ui approve positive right labeled icon button" :class="{'loading': this.location_loading}"
@click="sendLocationProcess">
Send
<i class="send icon"></i>
</div>
</div>
</div>
<!-- Modal SendRevoke -->
<div class="ui small modal" id="modalSendRevoke">
<i class="close icon"></i>
@ -468,7 +519,7 @@
<div class="field">
<label>Revoke Message ID</label>
<input v-model="revoke_message_id" type="text" placeholder="Please enter your message id"
aria-label="card_phone">
aria-label="message id">
</div>
</form>
</div>
@ -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')
</script>
</body>
Loading…
Cancel
Save