Browse Source

feat: new api send audio

pull/110/head
Aldino Kemal 2 years ago
parent
commit
5f68ce12ba
  1. 2
      .github/workflows/deploy-linux.yml
  2. 2
      .github/workflows/deploy-mac.yml
  3. 2
      .github/workflows/deploy-windows.yml
  4. 1
      readme.md
  5. 2
      src/config/settings.go
  6. 8
      src/domains/send/audio.go
  7. 1
      src/domains/send/send.go
  8. 4
      src/go.mod
  9. 4
      src/go.sum
  10. 11
      src/internal/rest/helpers/common.go
  11. 23
      src/internal/rest/send.go
  12. 17
      src/pkg/error/whatsapp_error.go
  13. 60
      src/services/send.go
  14. 42
      src/validations/send_validation.go

2
.github/workflows/deploy-linux.yml

@ -22,7 +22,7 @@ jobs:
- name: Golang Installation
uses: actions/setup-go@v3
with:
go-version: '1.20'
go-version: '1.21'
- name: Golang setup dependency
run: |
go version

2
.github/workflows/deploy-mac.yml

@ -21,7 +21,7 @@ jobs:
- name: Golang Installation
uses: actions/setup-go@v3
with:
go-version: '1.20'
go-version: '1.21'
- name: Golang setup dependency
run: |
go version

2
.github/workflows/deploy-windows.yml

@ -34,7 +34,7 @@ jobs:
- name: Golang Installation
uses: actions/setup-go@v3
with:
go-version: '1.20'
go-version: '1.21'
- name: Golang setup dependency
run: |
go version

1
readme.md

@ -104,6 +104,7 @@ API using [openapi-generator](https://openapi-generator.tech/#try)
| ✅ | User My Privacy Setting | GET | /user/my/privacy |
| ✅ | Send Message | POST | /send/message |
| ✅ | Send Image | POST | /send/image |
| ✅ | Send Audio | POST | /send/audio |
| ✅ | Send File | POST | /send/file |
| ✅ | Send Video | POST | /send/video |
| ✅ | Send Contact | POST | /send/contact |

2
src/config/settings.go

@ -6,7 +6,7 @@ import (
)
var (
AppVersion = "v4.8.4"
AppVersion = "v4.9.0"
AppPort = "3000"
AppDebug = false
AppOs = fmt.Sprintf("AldinoKemal")

8
src/domains/send/audio.go

@ -0,0 +1,8 @@
package send
import "mime/multipart"
type AudioRequest struct {
Phone string `json:"phone" form:"phone"`
Audio *multipart.FileHeader `json:"Audio" form:"Audio"`
}

1
src/domains/send/send.go

@ -12,6 +12,7 @@ type ISendService interface {
SendContact(ctx context.Context, request ContactRequest) (response GenericResponse, err error)
SendLink(ctx context.Context, request LinkRequest) (response GenericResponse, err error)
SendLocation(ctx context.Context, request LocationRequest) (response GenericResponse, err error)
SendAudio(ctx context.Context, request AudioRequest) (response GenericResponse, err error)
}
type GenericResponse struct {

4
src/go.mod

@ -12,14 +12,14 @@ require (
github.com/google/uuid v1.6.0
github.com/h2non/bimg v1.1.9
github.com/markbates/pkger v0.17.1
github.com/mattn/go-sqlite3 v1.14.20
github.com/mattn/go-sqlite3 v1.14.22
github.com/sirupsen/logrus v1.9.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4
github.com/valyala/fasthttp v1.51.0
go.mau.fi/libsignal v0.1.0
go.mau.fi/whatsmeow v0.0.0-20240129221825-0bb41340eb03
go.mau.fi/whatsmeow v0.0.0-20240201213949-57f290eebe9b
google.golang.org/protobuf v1.32.0
)

4
src/go.sum

@ -78,6 +78,8 @@ github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbW
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.20 h1:BAZ50Ns0OFBNxdAqFhbZqdPcht1Xlb16pDCqkq1spr0=
github.com/mattn/go-sqlite3 v1.14.20/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@ -119,6 +121,8 @@ go.mau.fi/util v0.3.0 h1:Lt3lbRXP6ZBqTINK0EieRWor3zEwwwrDT14Z5N8RUCs=
go.mau.fi/util v0.3.0/go.mod h1:9dGsBCCbZJstx16YgnVMVi3O2bOizELoKpugLD4FoGs=
go.mau.fi/whatsmeow v0.0.0-20240129221825-0bb41340eb03 h1:EWoQvfZydwqrRK6bYPlqQaLYAOJ8MW//ut6a/x2xlyw=
go.mau.fi/whatsmeow v0.0.0-20240129221825-0bb41340eb03/go.mod h1:5xTtHNaZpGni6z6aE1iEopjW7wNgsKcolZxZrOujK9M=
go.mau.fi/whatsmeow v0.0.0-20240201213949-57f290eebe9b h1:4d7OK8g0F3T92MAcNySmXRZzEdw0OdsWpWzbxeNjJHM=
go.mau.fi/whatsmeow v0.0.0-20240201213949-57f290eebe9b/go.mod h1:5xTtHNaZpGni6z6aE1iEopjW7wNgsKcolZxZrOujK9M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=

11
src/internal/rest/helpers/common.go

@ -3,6 +3,7 @@ package helpers
import (
"context"
domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app"
"mime/multipart"
"time"
)
@ -10,3 +11,13 @@ func SetAutoConnectAfterBooting(service domainApp.IAppService) {
time.Sleep(2 * time.Second)
_ = service.Reconnect(context.Background())
}
func MultipartFormFileHeaderToBytes(fileHeader *multipart.FileHeader) []byte {
file, _ := fileHeader.Open()
defer file.Close()
fileBytes := make([]byte, fileHeader.Size)
_, _ = file.Read(fileBytes)
return fileBytes
}

23
src/internal/rest/send.go

@ -20,6 +20,7 @@ func InitRestSend(app *fiber.App, service domainSend.ISendService) Send {
app.Post("/send/contact", rest.SendContact)
app.Post("/send/link", rest.SendLink)
app.Post("/send/location", rest.SendLocation)
app.Post("/send/audio", rest.SendAudio)
return rest
}
@ -162,3 +163,25 @@ func (controller *Send) SendLocation(c *fiber.Ctx) error {
Results: response,
})
}
func (controller *Send) SendAudio(c *fiber.Ctx) error {
var request domainSend.AudioRequest
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)
audio, err := c.FormFile("audio")
utils.PanicIfNeeded(err)
request.Audio = audio
whatsapp.SanitizePhone(&request.Phone)
response, err := controller.Service.SendAudio(c.UserContext(), request)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})
}

17
src/pkg/error/whatsapp_error.go

@ -53,6 +53,23 @@ func (e WaCliError) StatusCode() int {
return http.StatusInternalServerError
}
type WaUploadMediaError string
// Error for complying the error interface
func (e WaUploadMediaError) Error() string {
return string(e)
}
// ErrCode will return the error code based on the error data type
func (e WaUploadMediaError) ErrCode() string {
return "UPLOAD_MEDIA_ERROR"
}
// StatusCode will return the HTTP status code based on the error data type
func (e WaUploadMediaError) StatusCode() int {
return http.StatusInternalServerError
}
const (
ErrInvalidJID = InvalidJID("your JID is invalid")
ErrWaCLI = WaCliError("your WhatsApp CLI is invalid or empty")

60
src/services/send.go

@ -6,6 +6,7 @@ import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
"github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app"
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send"
"github.com/aldinokemal/go-whatsapp-web-multidevice/internal/rest/helpers"
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp"
@ -188,18 +189,14 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File
return response, err
}
oriFilePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.File.Filename)
err = fasthttp.SaveMultipartFile(request.File, oriFilePath)
if err != nil {
return response, err
}
fileBytes := helpers.MultipartFormFileHeaderToBytes(request.File)
fileMimeType := http.DetectContentType(fileBytes)
// Send to WA server
dataWaFile, err := os.ReadFile(oriFilePath)
if err != nil {
return response, err
}
uploadedFile, err := service.WaCli.Upload(context.Background(), dataWaFile, whatsmeow.MediaDocument)
uploadedFile, err := service.WaCli.Upload(context.Background(), fileBytes, whatsmeow.MediaDocument)
if err != nil {
fmt.Printf("Failed to upload file: %v", err)
return response, err
@ -207,7 +204,7 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File
msg := &waProto.Message{DocumentMessage: &waProto.DocumentMessage{
Url: proto.String(uploadedFile.URL),
Mimetype: proto.String(http.DetectContentType(dataWaFile)),
Mimetype: proto.String(fileMimeType),
Title: proto.String(request.File.Filename),
FileSha256: uploadedFile.FileSHA256,
FileLength: proto.Uint64(uploadedFile.FileLength),
@ -218,12 +215,6 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File
Caption: proto.String(request.Caption),
}}
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg)
go func() {
errDelete := utils.RemoveFile(0, oriFilePath)
if errDelete != nil {
fmt.Println(errDelete)
}
}()
if err != nil {
return response, err
}
@ -425,3 +416,44 @@ func (service serviceSend) SendLocation(ctx context.Context, request domainSend.
response.Status = fmt.Sprintf("Send location success %s (server timestamp: %s)", request.Phone, ts.Timestamp.String())
return response, nil
}
func (service serviceSend) SendAudio(ctx context.Context, request domainSend.AudioRequest) (response domainSend.GenericResponse, err error) {
err = validations.ValidateSendAudio(ctx, request)
if err != nil {
return response, err
}
dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone)
if err != nil {
return response, err
}
autioBytes := helpers.MultipartFormFileHeaderToBytes(request.Audio)
audioMimeType := http.DetectContentType(autioBytes)
audioUploaded, err := service.WaCli.Upload(ctx, autioBytes, whatsmeow.MediaAudio)
if err != nil {
err = pkgError.WaUploadMediaError(fmt.Sprintf("Failed to upload audio: %v", err))
return response, err
}
msg := &waProto.Message{
AudioMessage: &waProto.AudioMessage{
Url: proto.String(audioUploaded.URL),
DirectPath: proto.String(audioUploaded.DirectPath),
Mimetype: proto.String(audioMimeType),
FileLength: proto.Uint64(audioUploaded.FileLength),
FileSha256: audioUploaded.FileSHA256,
FileEncSha256: audioUploaded.FileEncSHA256,
MediaKey: audioUploaded.MediaKey,
},
}
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg)
if err != nil {
return response, err
}
response.MessageID = ts.ID
response.Status = fmt.Sprintf("Send audio success %s (server timestamp: %s)", request.Phone, ts.Timestamp.String())
return response, nil
}

42
src/validations/send_validation.go

@ -133,3 +133,45 @@ func ValidateSendLocation(ctx context.Context, request domainSend.LocationReques
return nil
}
func ValidateSendAudio(ctx context.Context, request domainSend.AudioRequest) error {
err := validation.ValidateStructWithContext(ctx, &request,
validation.Field(&request.Phone, validation.Required),
validation.Field(&request.Audio, validation.Required),
)
if err != nil {
return pkgError.ValidationError(err.Error())
}
availableMimes := map[string]bool{
"audio/aac": true,
"audio/amr": true,
"audio/flac": true,
"audio/m4a": true,
"audio/m4r": true,
"audio/mp3": true,
"audio/mpeg": true,
"audio/ogg": true,
"audio/wma": true,
"audio/x-ms-wma": true,
"audio/wav": true,
"audio/vnd.wav": true,
"audio/vnd.wave": true,
"audio/wave": true,
"audio/x-pn-wav": true,
"audio/x-wav": true,
}
availableMimesStr := ""
for k := range availableMimes {
availableMimesStr += k + ","
}
if !availableMimes[request.Audio.Header.Get("Content-Type")] {
return pkgError.ValidationError(fmt.Sprintf("your audio type is not allowed. please use (%s)", availableMimesStr))
}
return nil
}
Loading…
Cancel
Save