Browse Source

feat: add send link (#29)

* feat: add send link

feat: add more response when send api

* feat: remove url

* feat: update open api

* chore: update docs
pull/31/head
Aldino Kemal 3 years ago
committed by GitHub
parent
commit
05ec7f827f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 111
      docs/openapi.yaml
  2. 8
      readme.md
  3. 2
      src/config/settings.go
  4. 3
      src/domains/send/contact.go
  5. 3
      src/domains/send/file.go
  6. 3
      src/domains/send/image.go
  7. 13
      src/domains/send/link.go
  8. 1
      src/domains/send/send.go
  9. 3
      src/domains/send/text.go
  10. 3
      src/domains/send/video.go
  11. 25
      src/internal/rest/send_controller.go
  12. 89
      src/services/send_service.go
  13. 16
      src/validations/send_validation.go

111
docs/openapi.yaml

@ -1,7 +1,7 @@
openapi: 3.0.0 openapi: 3.0.0
info: info:
title: WhatsApp API MultiDevice title: WhatsApp API MultiDevice
version: 2.3.0
version: 2.4.0
description: This API is used for sending whatsapp via API description: This API is used for sending whatsapp via API
servers: servers:
- url: http://localhost:3000 - url: http://localhost:3000
@ -15,6 +15,7 @@ tags:
paths: paths:
/app/login: /app/login:
get: get:
operationId: authLogin
tags: tags:
- auth - auth
summary: Login to whatsapp server summary: Login to whatsapp server
@ -47,6 +48,7 @@ paths:
http://localhost:3000/statics/images/qrcode/scan-qr-b0b7bb43-9a22-455a-814f-5a225c743310.png http://localhost:3000/statics/images/qrcode/scan-qr-b0b7bb43-9a22-455a-814f-5a225c743310.png
/app/logout: /app/logout:
get: get:
operationId: authLogout
tags: tags:
- auth - auth
summary: Remove database and logout summary: Remove database and logout
@ -96,6 +98,7 @@ paths:
example: the store doesn't contain a device JID example: the store doesn't contain a device JID
/app/reconnect: /app/reconnect:
get: get:
operationId: authReconnect
tags: tags:
- auth - auth
summary: Reconnecting to whatsapp server summary: Reconnecting to whatsapp server
@ -125,6 +128,7 @@ paths:
results: null results: null
/user/info: /user/info:
get: get:
operationId: userInfo
tags: tags:
- user - user
summary: User Info summary: User Info
@ -241,6 +245,7 @@ paths:
results: null results: null
/user/avatar: /user/avatar:
get: get:
operationId: userAvatar
tags: tags:
- user - user
summary: User Avatar summary: User Avatar
@ -303,6 +308,7 @@ paths:
results: null results: null
/user/my/privacy: /user/my/privacy:
get: get:
operationId: userMyPrivacy
tags: tags:
- user - user
summary: User My Privacy Setting summary: User My Privacy Setting
@ -368,6 +374,7 @@ paths:
results: null results: null
/user/my/groups: /user/my/groups:
get: get:
operationId: userMyGroups
tags: tags:
- user - user
summary: User My List Groups summary: User My List Groups
@ -397,6 +404,7 @@ paths:
results: null results: null
/send/message: /send/message:
post: post:
operationId: sendMessage
tags: tags:
- send - send
summary: Send Message summary: Send Message
@ -491,6 +499,7 @@ paths:
results: null results: null
/send/image: /send/image:
post: post:
operationId: sendImage
tags: tags:
- send - send
summary: Send Image summary: Send Image
@ -594,6 +603,7 @@ paths:
results: null results: null
/send/file: /send/file:
post: post:
operationId: sendFile
tags: tags:
- send - send
summary: Send File summary: Send File
@ -691,6 +701,7 @@ paths:
results: null results: null
/send/video: /send/video:
post: post:
operationId: sendVideo
tags: tags:
- send - send
summary: Send Video summary: Send Video
@ -793,6 +804,7 @@ paths:
results: null results: null
/send/contact: /send/contact:
post: post:
operationId: sendContact
tags: tags:
- send - send
summary: Send Contact summary: Send Contact
@ -887,4 +899,101 @@ paths:
code: 500 code: 500
message: you are not loggin message: you are not loggin
results: null results: null
/send/link:
post:
operationId: sendLink
tags:
- send
summary: Send Link
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
phone:
type: integer
example: '6289685024051'
link:
type: string
example: "https://google.com"
caption:
type: string
example: 'Halo ini contoh caption'
type:
type: string
example: 'user'
description: 'user/group | default: user'
responses:
'200':
description: OK
headers:
Date:
schema:
type: string
example: Fri, 11 Feb 2022 03:42:57 GMT
Content-Type:
schema:
type: string
example: application/json
Content-Length:
schema:
type: integer
example: '123'
content:
application/json:
schema:
type: object
example:
code: 200
message: Success
results:
status: >-
Link sent to 6289685024051@s.whatsapp.net (server timestamp: 2022-05-17 14:39:29 +0700 WIB)
'400':
description: Bad Request
headers:
Date:
schema:
type: string
example: Fri, 11 Feb 2022 03:02:17 GMT
Content-Type:
schema:
type: string
example: application/json
Content-Length:
schema:
type: integer
example: '70'
content:
application/json:
schema:
type: object
example:
code: 400
message: 'phone: cannot be blank.'
results: null
'500':
description: Internal Server Error
headers:
Date:
schema:
type: string
example: Fri, 11 Feb 2022 03:02:48 GMT
Content-Type:
schema:
type: string
example: application/json
Content-Length:
schema:
type: integer
example: '58'
content:
application/json:
schema:
type: object
example:
code: 500
message: you are not loggin
results: null

8
readme.md

@ -1,4 +1,5 @@
## Go Whatsapp API Multi Device Version ## Go Whatsapp API Multi Device Version
[![buddy pipeline](https://app.buddy.works/aldinokemal/go-whatsapp-web-multidevice/pipelines/pipeline/423077/badge.svg?token=a951a4546fe3f54079e678cc9d0eea12069fbdc21f8ed5ea22e1e95c4f63215f "buddy pipeline")](https://app.buddy.works/aldinokemal/go-whatsapp-web-multidevice/pipelines/pipeline/423077) [![buddy pipeline](https://app.buddy.works/aldinokemal/go-whatsapp-web-multidevice/pipelines/pipeline/423077/badge.svg?token=a951a4546fe3f54079e678cc9d0eea12069fbdc21f8ed5ea22e1e95c4f63215f "buddy pipeline")](https://app.buddy.works/aldinokemal/go-whatsapp-web-multidevice/pipelines/pipeline/423077)
[![release windows](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/deploy-windows.yml/badge.svg "release windows")](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/deploy-windows.yml) [![release windows](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/deploy-windows.yml/badge.svg "release windows")](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/deploy-windows.yml)
[![release linux](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/deploy-linux.yml/badge.svg "release linux")](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/deploy-linux.yml) [![release linux](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/deploy-linux.yml/badge.svg "release linux")](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/deploy-linux.yml)
@ -10,7 +11,7 @@
- Compress image before send - Compress image before send
- Compress video before send - Compress video before send
- Change OS name become your app (it's the device name when connect via mobile) - Change OS name become your app (it's the device name when connect via mobile)
- `--os=Chrome` or `--os=MyApplication`
- `--os=Chrome` or `--os=MyApplication`
- Basic Auth - Basic Auth
- `--basic-auth=kemal:secret`, or you can simplify - `--basic-auth=kemal:secret`, or you can simplify
- `-b=kemal:secret` - `-b=kemal:secret`
@ -19,7 +20,7 @@
- `--debug true` - `--debug true`
- Auto reply message - Auto reply message
- `--autoreply="Don't reply this message"` - `--autoreply="Don't reply this message"`
- Webhook for received message
- Webhook for received message
- `--webhook="http://yourwebhook.site/handler"`, or you can simplify - `--webhook="http://yourwebhook.site/handler"`, or you can simplify
- `-w="http://yourwebhook.site/handler"` - `-w="http://yourwebhook.site/handler"`
- For more command `./main --help` - For more command `./main --help`
@ -100,7 +101,8 @@ You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API
| ✅ | Send Image | POST | /send/image | | ✅ | Send Image | POST | /send/image |
| ✅ | Send File | POST | /send/file | | ✅ | Send File | POST | /send/file |
| ✅ | Send Video | POST | /send/video | | ✅ | Send Video | POST | /send/video |
| ✅ | Send Contact | POST | /send/contact |
| ✅ | Send Contact | POST | /send/contact |
| ✅ | Send Link | POST | /send/link |
``` ```
✅ = Available ✅ = Available

2
src/config/settings.go

@ -6,7 +6,7 @@ import (
) )
var ( var (
AppVersion = "v3.7.1"
AppVersion = "v3.10.0"
AppPort = "3000" AppPort = "3000"
AppDebug = false AppDebug = false
AppOs = fmt.Sprintf("AldinoKemal") AppOs = fmt.Sprintf("AldinoKemal")

3
src/domains/send/contact.go

@ -8,5 +8,6 @@ type ContactRequest struct {
} }
type ContactResponse struct { type ContactResponse struct {
Status string `json:"status"`
MessageID string `json:"message_id"`
Status string `json:"status"`
} }

3
src/domains/send/file.go

@ -9,5 +9,6 @@ type FileRequest struct {
} }
type FileResponse struct { type FileResponse struct {
Status string `json:"status"`
MessageID string `json:"message_id"`
Status string `json:"status"`
} }

3
src/domains/send/image.go

@ -12,5 +12,6 @@ type ImageRequest struct {
} }
type ImageResponse struct { type ImageResponse struct {
Status string `json:"status"`
MessageID string `json:"message_id"`
Status string `json:"status"`
} }

13
src/domains/send/link.go

@ -0,0 +1,13 @@
package send
type LinkRequest struct {
Phone string `json:"phone" form:"phone"`
Caption string `json:"caption"`
Link string `json:"link"`
Type Type `json:"type" form:"type"`
}
type LinkResponse struct {
MessageID string `json:"message_id"`
Status string `json:"status"`
}

1
src/domains/send/send.go

@ -15,4 +15,5 @@ type ISendService interface {
SendFile(ctx context.Context, request FileRequest) (response FileResponse, err error) SendFile(ctx context.Context, request FileRequest) (response FileResponse, err error)
SendVideo(ctx context.Context, request VideoRequest) (response VideoResponse, err error) SendVideo(ctx context.Context, request VideoRequest) (response VideoResponse, err error)
SendContact(ctx context.Context, request ContactRequest) (response ContactResponse, err error) SendContact(ctx context.Context, request ContactRequest) (response ContactResponse, err error)
SendLink(ctx context.Context, request LinkRequest) (response LinkResponse, err error)
} }

3
src/domains/send/text.go

@ -7,5 +7,6 @@ type MessageRequest struct {
} }
type MessageResponse struct { type MessageResponse struct {
Status string `json:"status"`
MessageID string `json:"message_id"`
Status string `json:"status"`
} }

3
src/domains/send/video.go

@ -12,5 +12,6 @@ type VideoRequest struct {
} }
type VideoResponse struct { type VideoResponse struct {
Status string `json:"status"`
MessageID string `json:"message_id"`
Status string `json:"status"`
} }

25
src/internal/rest/send_controller.go

@ -18,6 +18,7 @@ func InitRestSend(app *fiber.App, service domainSend.ISendService) Send {
app.Post("/send/file", rest.SendFile) app.Post("/send/file", rest.SendFile)
app.Post("/send/video", rest.SendVideo) app.Post("/send/video", rest.SendVideo)
app.Post("/send/contact", rest.SendContact) app.Post("/send/contact", rest.SendContact)
app.Post("/send/link", rest.SendLink)
return rest return rest
} }
@ -158,3 +159,27 @@ func (controller *Send) SendContact(c *fiber.Ctx) error {
Results: response, Results: response,
}) })
} }
func (controller *Send) SendLink(c *fiber.Ctx) error {
var request domainSend.LinkRequest
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)
err = validations.ValidateSendLink(request)
utils.PanicIfNeeded(err)
if request.Type == domainSend.TypeGroup {
request.Phone = request.Phone + "@g.us"
} else {
request.Phone = request.Phone + "@s.whatsapp.net"
}
response, err := controller.Service.SendLink(c.UserContext(), request)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Code: 200,
Message: response.Status,
Results: response,
})
}

89
src/services/send_service.go

@ -35,13 +35,16 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess
if !ok { if !ok {
return response, errors.New("invalid JID " + request.Phone) return response, errors.New("invalid JID " + request.Phone)
} }
msgId := whatsmeow.GenerateMessageID()
msg := &waProto.Message{Conversation: proto.String(request.Message)} msg := &waProto.Message{Conversation: proto.String(request.Message)}
ts, err := service.WaCli.SendMessage(ctx, recipient, "", msg)
ts, err := service.WaCli.SendMessage(ctx, recipient, msgId, msg)
if err != nil { if err != nil {
return response, err return response, err
} else {
response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts)
} }
response.MessageID = msgId
response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil return response, nil
} }
@ -111,6 +114,7 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima
} }
dataWaThumbnail, err := os.ReadFile(imageThumbnail) dataWaThumbnail, err := os.ReadFile(imageThumbnail)
msgId := whatsmeow.GenerateMessageID()
msg := &waProto.Message{ImageMessage: &waProto.ImageMessage{ msg := &waProto.Message{ImageMessage: &waProto.ImageMessage{
JpegThumbnail: dataWaThumbnail, JpegThumbnail: dataWaThumbnail,
Caption: proto.String(dataWaCaption), Caption: proto.String(dataWaCaption),
@ -123,7 +127,7 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima
FileLength: proto.Uint64(uint64(len(dataWaImage))), FileLength: proto.Uint64(uint64(len(dataWaImage))),
ViewOnce: proto.Bool(request.ViewOnce), ViewOnce: proto.Bool(request.ViewOnce),
}} }}
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg)
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg)
go func() { go func() {
errDelete := utils.RemoveFile(0, deletedItems...) errDelete := utils.RemoveFile(0, deletedItems...)
if errDelete != nil { if errDelete != nil {
@ -132,10 +136,11 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima
}() }()
if err != nil { if err != nil {
return response, err return response, err
} else {
response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
} }
response.MessageID = msgId
response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
} }
func (service serviceSend) SendFile(ctx context.Context, request domainSend.FileRequest) (response domainSend.FileResponse, err error) { func (service serviceSend) SendFile(ctx context.Context, request domainSend.FileRequest) (response domainSend.FileResponse, err error) {
@ -162,6 +167,7 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File
return response, err return response, err
} }
msgId := whatsmeow.GenerateMessageID()
msg := &waProto.Message{DocumentMessage: &waProto.DocumentMessage{ msg := &waProto.Message{DocumentMessage: &waProto.DocumentMessage{
Url: proto.String(uploadedFile.URL), Url: proto.String(uploadedFile.URL),
Mimetype: proto.String(http.DetectContentType(dataWaFile)), Mimetype: proto.String(http.DetectContentType(dataWaFile)),
@ -173,7 +179,7 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File
FileEncSha256: uploadedFile.FileEncSHA256, FileEncSha256: uploadedFile.FileEncSHA256,
DirectPath: proto.String(uploadedFile.DirectPath), DirectPath: proto.String(uploadedFile.DirectPath),
}} }}
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg)
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg)
go func() { go func() {
errDelete := utils.RemoveFile(0, oriFilePath) errDelete := utils.RemoveFile(0, oriFilePath)
if errDelete != nil { if errDelete != nil {
@ -182,10 +188,11 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File
}() }()
if err != nil { if err != nil {
return response, err return response, err
} else {
response.Status = fmt.Sprintf("Document sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
} }
response.MessageID = msgId
response.Status = fmt.Sprintf("Document sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
} }
func (service serviceSend) SendVideo(ctx context.Context, request domainSend.VideoRequest) (response domainSend.VideoResponse, err error) { func (service serviceSend) SendVideo(ctx context.Context, request domainSend.VideoRequest) (response domainSend.VideoResponse, err error) {
@ -250,7 +257,7 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid
if err != nil { if err != nil {
return response, err return response, err
} }
uploadedFile, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo)
uploaded, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo)
if err != nil { if err != nil {
fmt.Printf("Failed to upload file: %v", err) fmt.Printf("Failed to upload file: %v", err)
return response, err return response, err
@ -260,19 +267,20 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid
return response, err return response, err
} }
msgId := whatsmeow.GenerateMessageID()
msg := &waProto.Message{VideoMessage: &waProto.VideoMessage{ msg := &waProto.Message{VideoMessage: &waProto.VideoMessage{
Url: proto.String(uploadedFile.URL),
Url: proto.String(uploaded.URL),
Mimetype: proto.String(http.DetectContentType(dataWaVideo)), Mimetype: proto.String(http.DetectContentType(dataWaVideo)),
Caption: proto.String(request.Caption), Caption: proto.String(request.Caption),
FileLength: proto.Uint64(uploadedFile.FileLength),
FileSha256: uploadedFile.FileSHA256,
FileEncSha256: uploadedFile.FileEncSHA256,
MediaKey: uploadedFile.MediaKey,
DirectPath: proto.String(uploadedFile.DirectPath),
FileLength: proto.Uint64(uploaded.FileLength),
FileSha256: uploaded.FileSHA256,
FileEncSha256: uploaded.FileEncSHA256,
MediaKey: uploaded.MediaKey,
DirectPath: proto.String(uploaded.DirectPath),
ViewOnce: proto.Bool(request.ViewOnce), ViewOnce: proto.Bool(request.ViewOnce),
JpegThumbnail: dataWaThumbnail, JpegThumbnail: dataWaThumbnail,
}} }}
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg)
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg)
go func() { go func() {
errDelete := utils.RemoveFile(0, deletedItems...) errDelete := utils.RemoveFile(0, deletedItems...)
if errDelete != nil { if errDelete != nil {
@ -281,10 +289,11 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid
}() }()
if err != nil { if err != nil {
return response, err return response, err
} else {
response.Status = fmt.Sprintf("Video sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
} }
response.MessageID = msgId
response.Status = fmt.Sprintf("Video sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
} }
func (service serviceSend) SendContact(ctx context.Context, request domainSend.ContactRequest) (response domainSend.ContactResponse, err error) { func (service serviceSend) SendContact(ctx context.Context, request domainSend.ContactRequest) (response domainSend.ContactResponse, err error) {
@ -296,6 +305,7 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C
} }
msgVCard := fmt.Sprintf("BEGIN:VCARD\nVERSION:3.0\nN:;%v;;;\nFN:%v\nTEL;type=CELL;waid=%v:+%v\nEND:VCARD", msgVCard := fmt.Sprintf("BEGIN:VCARD\nVERSION:3.0\nN:;%v;;;\nFN:%v\nTEL;type=CELL;waid=%v:+%v\nEND:VCARD",
request.ContactName, request.ContactName, request.ContactPhone, request.ContactPhone) request.ContactName, request.ContactName, request.ContactPhone, request.ContactPhone)
msgId := whatsmeow.GenerateMessageID()
msg := &waProto.Message{ContactMessage: &waProto.ContactMessage{ msg := &waProto.Message{ContactMessage: &waProto.ContactMessage{
DisplayName: proto.String(request.ContactName), DisplayName: proto.String(request.ContactName),
Vcard: proto.String(msgVCard), Vcard: proto.String(msgVCard),
@ -303,8 +313,39 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C
ts, err := service.WaCli.SendMessage(ctx, recipient, "", msg) ts, err := service.WaCli.SendMessage(ctx, recipient, "", msg)
if err != nil { if err != nil {
return response, err return response, err
} else {
response.Status = fmt.Sprintf("Contact sent to %s (server timestamp: %s)", request.Phone, ts)
} }
response.MessageID = msgId
response.Status = fmt.Sprintf("Contact sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
}
func (service serviceSend) SendLink(ctx context.Context, request domainSend.LinkRequest) (response domainSend.LinkResponse, err error) {
utils.MustLogin(service.WaCli)
recipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
msgId := whatsmeow.GenerateMessageID()
msg := &waProto.Message{ExtendedTextMessage: &waProto.ExtendedTextMessage{
Text: proto.String(request.Caption),
MatchedText: proto.String(request.Caption),
CanonicalUrl: proto.String(request.Link),
ContextInfo: &waProto.ContextInfo{
ActionLink: &waProto.ActionLink{
Url: proto.String(request.Link),
ButtonTitle: proto.String(request.Caption),
},
},
}}
ts, err := service.WaCli.SendMessage(ctx, recipient, msgId, msg)
if err != nil {
return response, err
}
response.MessageID = msgId
response.Status = fmt.Sprintf("Link sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil return response, nil
} }

16
src/validations/send_validation.go

@ -113,3 +113,19 @@ func ValidateSendContact(request domainSend.ContactRequest) {
}) })
} }
} }
func ValidateSendLink(request domainSend.LinkRequest) error {
err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)),
validation.Field(&request.Link, validation.Required),
validation.Field(&request.Caption, validation.Required),
)
if err != nil {
return utils.ValidationError{
Message: err.Error(),
}
}
return nil
}
Loading…
Cancel
Save