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 - name: Golang Installation
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '1.20'
go-version: '1.21'
- name: Golang setup dependency - name: Golang setup dependency
run: | run: |
go version go version

2
.github/workflows/deploy-mac.yml

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

2
.github/workflows/deploy-windows.yml

@ -34,7 +34,7 @@ jobs:
- name: Golang Installation - name: Golang Installation
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '1.20'
go-version: '1.21'
- name: Golang setup dependency - name: Golang setup dependency
run: | run: |
go version 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 | | ✅ | User My Privacy Setting | GET | /user/my/privacy |
| ✅ | Send Message | POST | /send/message | | ✅ | Send Message | POST | /send/message |
| ✅ | Send Image | POST | /send/image | | ✅ | Send Image | POST | /send/image |
| ✅ | Send Audio | POST | /send/audio |
| ✅ | 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 |

2
src/config/settings.go

@ -6,7 +6,7 @@ import (
) )
var ( var (
AppVersion = "v4.8.4"
AppVersion = "v4.9.0"
AppPort = "3000" AppPort = "3000"
AppDebug = false AppDebug = false
AppOs = fmt.Sprintf("AldinoKemal") 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) SendContact(ctx context.Context, request ContactRequest) (response GenericResponse, err error)
SendLink(ctx context.Context, request LinkRequest) (response GenericResponse, err error) SendLink(ctx context.Context, request LinkRequest) (response GenericResponse, err error)
SendLocation(ctx context.Context, request LocationRequest) (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 { type GenericResponse struct {

4
src/go.mod

@ -12,14 +12,14 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/h2non/bimg v1.1.9 github.com/h2non/bimg v1.1.9
github.com/markbates/pkger v0.17.1 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/sirupsen/logrus v1.9.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/valyala/fasthttp v1.51.0 github.com/valyala/fasthttp v1.51.0
go.mau.fi/libsignal v0.1.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 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.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 h1:BAZ50Ns0OFBNxdAqFhbZqdPcht1Xlb16pDCqkq1spr0=
github.com/mattn/go-sqlite3 v1.14.20/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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= 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/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 h1:EWoQvfZydwqrRK6bYPlqQaLYAOJ8MW//ut6a/x2xlyw=
go.mau.fi/whatsmeow v0.0.0-20240129221825-0bb41340eb03/go.mod h1:5xTtHNaZpGni6z6aE1iEopjW7wNgsKcolZxZrOujK9M= 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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=

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

@ -3,6 +3,7 @@ package helpers
import ( import (
"context" "context"
domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app"
"mime/multipart"
"time" "time"
) )
@ -10,3 +11,13 @@ func SetAutoConnectAfterBooting(service domainApp.IAppService) {
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
_ = service.Reconnect(context.Background()) _ = 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/contact", rest.SendContact)
app.Post("/send/link", rest.SendLink) app.Post("/send/link", rest.SendLink)
app.Post("/send/location", rest.SendLocation) app.Post("/send/location", rest.SendLocation)
app.Post("/send/audio", rest.SendAudio)
return rest return rest
} }
@ -162,3 +163,25 @@ func (controller *Send) SendLocation(c *fiber.Ctx) error {
Results: response, 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 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 ( const (
ErrInvalidJID = InvalidJID("your JID is invalid") ErrInvalidJID = InvalidJID("your JID is invalid")
ErrWaCLI = WaCliError("your WhatsApp CLI is invalid or empty") 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/config"
"github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app"
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" 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" 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/utils"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "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 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 // Send to WA server
dataWaFile, err := os.ReadFile(oriFilePath)
if err != nil { if err != nil {
return response, err 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 { 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
@ -207,7 +204,7 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File
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(fileMimeType),
Title: proto.String(request.File.Filename), Title: proto.String(request.File.Filename),
FileSha256: uploadedFile.FileSHA256, FileSha256: uploadedFile.FileSHA256,
FileLength: proto.Uint64(uploadedFile.FileLength), FileLength: proto.Uint64(uploadedFile.FileLength),
@ -218,12 +215,6 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File
Caption: proto.String(request.Caption), Caption: proto.String(request.Caption),
}} }}
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg) ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg)
go func() {
errDelete := utils.RemoveFile(0, oriFilePath)
if errDelete != nil {
fmt.Println(errDelete)
}
}()
if err != nil { if err != nil {
return response, err 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()) response.Status = fmt.Sprintf("Send location success %s (server timestamp: %s)", request.Phone, ts.Timestamp.String())
return response, nil 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 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