From 9a301c6068c515cc18bf48c577cc7e2102404d81 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Tue, 17 May 2022 18:44:01 +0700 Subject: [PATCH] feat: add send video (#15) * feat: add send rest * feat: add view_once when sending video * feat: upgrade body limit * feat: change statics folder * feat: remove copyright * feat: add thumbnails in send video * fix: deleting file * feat: add params compress video * chore: update openapi * chore: update readme * chore: update readme for installation * chore: update docker and document * feat: fix validation and update dashboard page * fix: docker config & utils code * chore: update docs * chore: update docs --- docker/golang.Dockerfile | 8 +- docs/openapi.yaml | 108 +++++++++- readme.md | 42 ++-- src/.air.toml | 2 +- src/cmd/root.go | 10 +- src/config/settings.go | 15 +- src/controllers/send_controller.go | 33 ++- src/go.mod | 1 + src/go.sum | 2 + src/main.go | 4 - src/services/send_service.go | 1 + src/services/send_service_impl.go | 113 +++++++++- src/statics/{images => }/qrcode/.gitignore | 0 src/statics/{images => }/senditems/.gitignore | 0 src/structs/send_struct.go | 17 +- src/utils/general.go | 12 +- src/validations/send_validation.go | 48 ++++- src/validations/send_validation_test.go | 21 -- src/views/index.html | 199 +++++++++++++----- 19 files changed, 506 insertions(+), 130 deletions(-) rename src/statics/{images => }/qrcode/.gitignore (100%) rename src/statics/{images => }/senditems/.gitignore (100%) diff --git a/docker/golang.Dockerfile b/docker/golang.Dockerfile index 8e276b0..047fbb5 100644 --- a/docker/golang.Dockerfile +++ b/docker/golang.Dockerfile @@ -2,22 +2,22 @@ # STEP 1 build executable binary ############################ FROM golang:alpine AS builder -RUN apk update && apk add --no-cache vips-dev gcc musl-dev gcompat +RUN apk update && apk add --no-cache vips-dev gcc musl-dev gcompat ffmpeg WORKDIR /whatsapp COPY ./src . # Fetch dependencies. RUN go mod download # Install pkger -RUN go install github.com/gobuffalo/packr/v2/packr2@latest +RUN go install github.com/markbates/pkger/cmd/pkger@latest # Build the binary. -RUN go build -o /app/whatsapp +RUN pkger && go build -o /app/whatsapp ############################# ## STEP 2 build a smaller image ############################# FROM alpine -RUN apk update && apk add --no-cache vips-dev +RUN apk update && apk add --no-cache vips-dev ffmpeg WORKDIR /app # Copy compiled from builder. COPY --from=builder /app/whatsapp /app/whatsapp diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 722fa9f..672fecb 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: WhatsApp API MultiDevice - version: 2.2.1 + version: 2.3.0 description: This API is used for sending whatsapp via API servers: - url: http://localhost:3000 @@ -606,9 +606,6 @@ paths: caption: type: string example: selamat malam - view_once: - type: boolean - example: 'false' file: type: string format: binary @@ -689,3 +686,106 @@ paths: code: 500 message: you are not loggin results: null + /send/video: + post: + tags: + - send + summary: Send Video + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + phone: + type: integer + example: '6289685024091' + caption: + type: string + example: ini contoh caption video + view_once: + type: boolean + example: 'false' + video: + type: string + format: binary + type: + type: string + example: 'user' + description: 'user/group | default: user' + compress: + type: boolean + example: 'false' + 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: >- + Video sent to 6289685024091@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 + diff --git a/readme.md b/readme.md index 265b160..2a4b1f1 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,7 @@ ### Feature - Send whatsapp via http API, [docs/openapi.yml](./docs/openapi.yaml) for more details - Compress image before send +- Compress video before send - Customizable port and debug mode - `--port 8000` - `--debug true` @@ -13,13 +14,16 @@ - Mac OS: - `brew install vips` + - `brew install ffmpeg` - `export CGO_CFLAGS_ALLOW="-Xpreprocessor"` - Linux: - `sudo apt update` - `sudo apt install libvips-dev` + - `sudo apt install ffmpeg` - Windows (not recomended, prefer using [WSL](https://docs.microsoft.com/en-us/windows/wsl/install)): - install vips library, or you can check here https://www.libvips.org/install.html - - add to [environment variable](https://www.google.com/search?q=windows+add+to+environment+path) + - install ffmpeg, download [here](https://www.ffmpeg.org/download.html#build-windows) + - add to vips & ffmpg to [environment variable](https://www.google.com/search?q=windows+add+to+environment+path) ### How to use @@ -55,7 +59,7 @@ 7. open `http://localhost:3000` in browser ### Production Mode (docker) -- `docker run --publish 3000:3000 --restart=always aldinokemal2104/go-whatsapp-web-multidevice` +- `docker run --publish=3000:3000 --name=whatsapp --restart=always --detach aldinokemal2104/go-whatsapp-web-multidevice --autoreply="Dont't reply this message please"` ### Production Mode (binary) - download binary from [release](https://github.com/aldinokemal/go-whatsapp-web-multidevice/releases) @@ -65,19 +69,20 @@ You can fork or edit this source code ! ### Current API You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API -| Feature | Menu | Method | URL | Payload | -|---------|-------------------------|--------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ✅ | Login | GET | /app/login | | -| ✅ | Logout | GET | /app/logout | | -| ✅ | Reconnect | GET | /app/reconnect | | -| ✅ | User Info | GET | /user/info |
Param Type Type Example
phonestringquerystring6289685024099
| -| ✅ | User Avatar | GET | /user/avatar |
Param Type Type Example
phonestringquerystring6289685024099
| -| ✅ | User My Group List | GET | /user/my/groups | | -| ✅ | User My Privacy Setting | GET | /user/my/privacy | | -| ✅ | Send Message (Text) | POST | /send/message |
Param Data Type Type Example
phonestringform-data6289685024099
messagestringform-dataHello guys this is testing
typestring (user/group)form-datauser
| -| ✅ | Send Message (Image) | POST | /send/image |
Param Type Type Example
phonestringform-data6289685024099
captionstringform-dataHello guys this is caption
view_onceboolform-datafalse
imagebinaryform-dataimage/jpg,image/jpeg,image/png
typestring (user/group)form-datauser
| -| ✅ | Send Message (File) | POST | /send/file |
ParamTypeTypeExample
phonestringform-data6289685024099
filebinaryform-dataany (max: 10MB)
typestring (user/group)form-datauser
| -| ❌ | Send Message (Video) | POST | /send/video |
ParamTypeTypeExample
phonestringform-data6289685024099
videobinaryform-datamp4/avi/mkv
typestring (user/group)form-datauser
| +| Feature | Menu | Method | URL | +|---------|-------------------------|--------|------------------| +| ✅ | Login | GET | /app/login | +| ✅ | Logout | GET | /app/logout | +| ✅ | Reconnect | GET | /app/reconnect | +| ✅ | User Info | GET | /user/info | +| ✅ | User Avatar | GET | /user/avatar | +| ✅ | User My Group List | GET | /user/my/groups | +| ✅ | User My Privacy Setting | GET | /user/my/privacy | +| ✅ | Send Message | POST | /send/message | +| ✅ | Send Image | POST | /send/image | +| ✅ | Send File | POST | /send/file | +| ✅ | Send Video | POST | /send/video | +| ❌ | Send Contact | POST | /send/contact | ``` ✅ = Available @@ -92,9 +97,10 @@ You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API 4. Send Image ![Send Image](https://i.ibb.co/HDVJZSN/Screen-Shot-2022-02-13-at-12-59-06.png) 5. Send File ![Send File](https://i.ibb.co/XxNnsQ8/Screen-Shot-2022-02-13-at-12-59-14.png) 6. User Info ![User Info](https://i.ibb.co/BC0mNT7/Screen-Shot-2022-02-13-at-13-00-57.png) -6. User Avatar ![User Avatar](https://i.ibb.co/TkzPbLZ/Screen-Shot-2022-02-13-at-13-01-39.png) -7. User Privacy ![User My Privacy](https://i.ibb.co/RQcC5m9/Screen-Shot-2022-02-13-at-12-58-47.png) -8. User Group ![List Group](https://i.ibb.co/jfkgKdG/Screen-Shot-2022-05-12-at-21-12-06.png) +7. User Avatar ![User Avatar](https://i.ibb.co/TkzPbLZ/Screen-Shot-2022-02-13-at-13-01-39.png) +8. User Privacy ![User My Privacy](https://i.ibb.co/RQcC5m9/Screen-Shot-2022-02-13-at-12-58-47.png) +9. User Group ![List Group](https://i.ibb.co/jfkgKdG/Screen-Shot-2022-05-12-at-21-12-06.png) +10. Auto Reply ![Auto Reply](https://i.ibb.co/D4rTytX/IMG-20220517-162500.jpg) ### Mac OS NOTE diff --git a/src/.air.toml b/src/.air.toml index da830ba..5b2841a 100644 --- a/src/.air.toml +++ b/src/.air.toml @@ -2,4 +2,4 @@ root = '.' tmp_dir = "tmp" [build] -exclude_dir = ["statics"] \ No newline at end of file +exclude_dir = ["statics", "views"] \ No newline at end of file diff --git a/src/cmd/root.go b/src/cmd/root.go index 5fb1b3a..cf73dbe 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -7,6 +7,7 @@ import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/middleware" "github.com/aldinokemal/go-whatsapp-web-multidevice/services" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + "github.com/dustin/go-humanize" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/logger" @@ -49,7 +50,7 @@ func runRest(cmd *cobra.Command, args []string) { engine := html.NewFileSystem(pkger.Dir("/views"), ".html") app := fiber.New(fiber.Config{ Views: engine, - BodyLimit: 10 * 1024 * 1024, + BodyLimit: 50 * 1024 * 1024, }) app.Static("/statics", "./statics") app.Use(middleware.Recovery()) @@ -79,7 +80,12 @@ func runRest(cmd *cobra.Command, args []string) { userController.Route(app) app.Get("/", func(ctx *fiber.Ctx) error { - return ctx.Render("index", fiber.Map{"AppHost": fmt.Sprintf("%s://%s", ctx.Protocol(), ctx.Hostname())}) + return ctx.Render("index", fiber.Map{ + "AppHost": fmt.Sprintf("%s://%s", ctx.Protocol(), ctx.Hostname()), + "AppVersion": config.AppVersion, + "MaxFileSize": humanize.Bytes(uint64(config.WhatsappSettingMaxFileSize)), + "MaxVideoSize": humanize.Bytes(uint64(config.WhatsappSettingMaxVideoSize)), + }) }) err = app.Listen(":" + config.AppPort) diff --git a/src/config/settings.go b/src/config/settings.go index 34dacbe..6956e2d 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -3,14 +3,17 @@ package config type Browser string var ( - AppPort string = "3000" - AppDebug bool = false + AppVersion string = "3.3.0" + AppPort string = "3000" + AppDebug bool = false - PathQrCode string = "statics/images/qrcode" - PathSendItems string = "statics/images/senditems" + PathQrCode string = "statics/qrcode" + PathSendItems string = "statics/senditems" DBName string = "hydrogenWaCli.db" - WhatsappLogLevel string = "ERROR" - WhatsappAutoReplyMessage string + WhatsappLogLevel string = "ERROR" + WhatsappAutoReplyMessage string + WhatsappSettingMaxFileSize int64 = 30000000 // 10MB + WhatsappSettingMaxVideoSize int64 = 30000000 // 30MB ) diff --git a/src/controllers/send_controller.go b/src/controllers/send_controller.go index 6e1aab6..3820c59 100644 --- a/src/controllers/send_controller.go +++ b/src/controllers/send_controller.go @@ -20,6 +20,7 @@ func (controller *SendController) Route(app *fiber.App) { app.Post("/send/message", controller.SendText) app.Post("/send/image", controller.SendImage) app.Post("/send/file", controller.SendFile) + app.Post("/send/video", controller.SendVideo) } func (controller *SendController) SendText(c *fiber.Ctx) error { @@ -93,7 +94,7 @@ func (controller *SendController) SendFile(c *fiber.Ctx) error { } else { request.Phone = request.Phone + "@s.whatsapp.net" } - + response, err := controller.Service.SendFile(c, request) utils.PanicIfNeeded(err) @@ -103,3 +104,33 @@ func (controller *SendController) SendFile(c *fiber.Ctx) error { Results: response, }) } + +func (controller *SendController) SendVideo(c *fiber.Ctx) error { + var request structs.SendVideoRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + video, err := c.FormFile("video") + utils.PanicIfNeeded(err) + + request.Video = video + + //add validation send image + validations.ValidateSendVideo(request) + + if request.Type == structs.TypeGroup { + request.Phone = request.Phone + "@g.us" + } else { + request.Phone = request.Phone + "@s.whatsapp.net" + } + + response, err := controller.Service.SendVideo(c, request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Code: 200, + Message: response.Status, + Results: response, + }) +} + diff --git a/src/go.mod b/src/go.mod index 97a1275..1719565 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,6 +3,7 @@ module github.com/aldinokemal/go-whatsapp-web-multidevice go 1.17 require ( + github.com/dustin/go-humanize v1.0.0 github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/gofiber/fiber/v2 v2.26.0 github.com/gofiber/template v1.6.22 diff --git a/src/go.sum b/src/go.sum index c4b018b..bd56f99 100644 --- a/src/go.sum +++ b/src/go.sum @@ -106,6 +106,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/src/main.go b/src/main.go index 5cda02b..389affa 100644 --- a/src/main.go +++ b/src/main.go @@ -1,7 +1,3 @@ -/* -Copyright © 2022 NAME HERE - -*/ package main import "github.com/aldinokemal/go-whatsapp-web-multidevice/cmd" diff --git a/src/services/send_service.go b/src/services/send_service.go index e3c4b5d..0ad41dc 100644 --- a/src/services/send_service.go +++ b/src/services/send_service.go @@ -9,4 +9,5 @@ type SendService interface { SendText(c *fiber.Ctx, request structs.SendMessageRequest) (response structs.SendMessageResponse, err error) SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, err error) SendFile(c *fiber.Ctx, request structs.SendFileRequest) (response structs.SendFileResponse, err error) + SendVideo(c *fiber.Ctx, request structs.SendVideoRequest) (response structs.SendVideoResponse, err error) } diff --git a/src/services/send_service_impl.go b/src/services/send_service_impl.go index e9a9a01..58f50dd 100644 --- a/src/services/send_service_impl.go +++ b/src/services/send_service_impl.go @@ -8,12 +8,14 @@ import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/structs" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/gofiber/fiber/v2" + fiberUtils "github.com/gofiber/fiber/v2/utils" "github.com/h2non/bimg" "go.mau.fi/whatsmeow" waProto "go.mau.fi/whatsmeow/binary/proto" "google.golang.org/protobuf/proto" "net/http" "os" + "os/exec" ) type SendServiceImpl struct { @@ -46,14 +48,15 @@ func (service SendServiceImpl) SendText(_ *fiber.Ctx, request structs.SendMessag func (service SendServiceImpl) SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, err error) { utils.MustLogin(service.WaCli) - // Resize image + // Save image to server oriImagePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.Image.Filename) err = c.SaveFile(request.Image, oriImagePath) if err != nil { return response, err } + // Resize image openImageBuffer, err := bimg.Read(oriImagePath) - newImage, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Height: 600, Embed: true}) + newImage, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Embed: true}) if err != nil { return response, err } @@ -63,7 +66,7 @@ func (service SendServiceImpl) SendImage(c *fiber.Ctx, request structs.SendImage if err != nil { return response, err } - + // Send to WA server dataWaCaption := request.Caption dataWaRecipient, ok := utils.ParseJID(request.Phone) @@ -96,7 +99,7 @@ func (service SendServiceImpl) SendImage(c *fiber.Ctx, request structs.SendImage go func() { errDelete := utils.RemoveFile(0, oriImagePath, newImagePath) if errDelete != nil { - fmt.Println(errDelete) + fmt.Println("error when deleting picture: ", errDelete) } }() if err != nil { @@ -110,7 +113,6 @@ func (service SendServiceImpl) SendImage(c *fiber.Ctx, request structs.SendImage func (service SendServiceImpl) SendFile(c *fiber.Ctx, request structs.SendFileRequest) (response structs.SendFileResponse, err error) { utils.MustLogin(service.WaCli) - // Resize image oriFilePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.File.Filename) err = c.SaveFile(request.File, oriFilePath) if err != nil { @@ -153,7 +155,106 @@ func (service SendServiceImpl) SendFile(c *fiber.Ctx, request structs.SendFileRe if err != nil { return response, err } else { - response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts) + response.Status = fmt.Sprintf("Document sent to %s (server timestamp: %s)", request.Phone, ts) + return response, nil + } +} + +func (service SendServiceImpl) SendVideo(c *fiber.Ctx, request structs.SendVideoRequest) (response structs.SendVideoResponse, err error) { + utils.MustLogin(service.WaCli) + + var ( + videoPath string + videoThumbnail string + deletedItems []string + ) + + generateUUID := fiberUtils.UUIDv4() + // Save video to server + oriVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+request.Video.Filename) + err = c.SaveFile(request.Video, oriVideoPath) + if err != nil { + return response, err + } + + // Get thumbnail video with ffmpeg + thumbnailVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+".png") + cmdThumbnail := exec.Command("ffmpeg", "-i", oriVideoPath, "-ss", "00:00:01.000", "-vframes", "1", thumbnailVideoPath) + err = cmdThumbnail.Run() + utils.PanicIfNeeded(err, "error when getting thumbnail") + + // Resize Thumbnail + openImageBuffer, err := bimg.Read(thumbnailVideoPath) + resize, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Embed: true}) + if err != nil { + return response, err + } + thumbnailResizeVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+"_resize.png") + err = bimg.Write(thumbnailResizeVideoPath, resize) + if err != nil { + return response, err + } + + deletedItems = append(deletedItems, thumbnailVideoPath) + deletedItems = append(deletedItems, thumbnailResizeVideoPath) + videoThumbnail = thumbnailResizeVideoPath + + if request.Compress { + compresVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+".mp4") + // Compress video with ffmpeg + cmdCompress := exec.Command("ffmpeg", "-i", oriVideoPath, "-strict", "-2", compresVideoPath) + err = cmdCompress.Run() + utils.PanicIfNeeded(err, "error when compress video") + + videoPath = compresVideoPath + deletedItems = append(deletedItems, compresVideoPath) + } else { + videoPath = oriVideoPath + deletedItems = append(deletedItems, oriVideoPath) + } + + //Send to WA server + dataWaRecipient, ok := utils.ParseJID(request.Phone) + if !ok { + return response, errors.New("invalid JID " + request.Phone) + } + dataWaVideo, err := os.ReadFile(videoPath) + if err != nil { + return response, err + } + uploadedFile, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo) + if err != nil { + fmt.Printf("Failed to upload file: %v", err) + return response, err + } + dataWaThumbnail, err := os.ReadFile(videoThumbnail) + if err != nil { + return response, err + } + + msg := &waProto.Message{VideoMessage: &waProto.VideoMessage{ + Url: proto.String(uploadedFile.URL), + Mimetype: proto.String(http.DetectContentType(dataWaVideo)), + Caption: proto.String(request.Caption), + FileLength: proto.Uint64(uploadedFile.FileLength), + FileSha256: uploadedFile.FileSHA256, + FileEncSha256: uploadedFile.FileEncSHA256, + MediaKey: uploadedFile.MediaKey, + DirectPath: proto.String(uploadedFile.DirectPath), + ViewOnce: proto.Bool(request.ViewOnce), + JpegThumbnail: dataWaThumbnail, + }} + ts, err := service.WaCli.SendMessage(dataWaRecipient, "", msg) + go func() { + errDelete := utils.RemoveFile(0, deletedItems...) + if errDelete != nil { + fmt.Println(errDelete) + } + }() + if err != nil { + return response, err + } else { + response.Status = fmt.Sprintf("Video sent to %s (server timestamp: %s)", request.Phone, ts) return response, nil } } diff --git a/src/statics/images/qrcode/.gitignore b/src/statics/qrcode/.gitignore similarity index 100% rename from src/statics/images/qrcode/.gitignore rename to src/statics/qrcode/.gitignore diff --git a/src/statics/images/senditems/.gitignore b/src/statics/senditems/.gitignore similarity index 100% rename from src/statics/images/senditems/.gitignore rename to src/statics/senditems/.gitignore diff --git a/src/structs/send_struct.go b/src/structs/send_struct.go index 631a95e..3ddd2a1 100644 --- a/src/structs/send_struct.go +++ b/src/structs/send_struct.go @@ -24,7 +24,7 @@ type SendImageRequest struct { Caption string `json:"caption" form:"caption"` Image *multipart.FileHeader `json:"image" form:"image"` ViewOnce bool `json:"view_once" form:"view_once"` - Type SendType `json:"type" form:"message"` + Type SendType `json:"type" form:"type"` } type SendImageResponse struct { @@ -34,9 +34,22 @@ type SendImageResponse struct { type SendFileRequest struct { Phone string `json:"phone" form:"phone"` File *multipart.FileHeader `json:"file" form:"file"` - Type SendType `json:"type" form:"message"` + Type SendType `json:"type" form:"type"` } type SendFileResponse struct { Status string `json:"status"` } + +type SendVideoRequest struct { + Phone string `json:"phone" form:"phone"` + Caption string `json:"caption" form:"caption"` + Video *multipart.FileHeader `json:"video" form:"video"` + Type SendType `json:"type" form:"type"` + ViewOnce bool `json:"view_once" form:"view_once"` + Compress bool `json:"compress"` +} + +type SendVideoResponse struct { + Status string `json:"status"` +} diff --git a/src/utils/general.go b/src/utils/general.go index 39b4110..f1c07d3 100644 --- a/src/utils/general.go +++ b/src/utils/general.go @@ -13,8 +13,12 @@ func RemoveFile(delaySecond int, paths ...string) error { } for _, path := range paths { - err := os.Remove(path) - return err + if path != "" { + err := os.Remove(path) + if err != nil { + return err + } + } } return nil } @@ -24,7 +28,9 @@ func CreateFolder(folderPath ...string) error { for _, folder := range folderPath { newFolder := filepath.Join(".", folder) err := os.MkdirAll(newFolder, os.ModePerm) - return err + if err != nil { + return err + } } return nil } diff --git a/src/validations/send_validation.go b/src/validations/send_validation.go index 11c73a1..d479fce 100644 --- a/src/validations/send_validation.go +++ b/src/validations/send_validation.go @@ -1,15 +1,17 @@ package validations import ( + "fmt" + "github.com/aldinokemal/go-whatsapp-web-multidevice/config" "github.com/aldinokemal/go-whatsapp-web-multidevice/structs" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + "github.com/dustin/go-humanize" validation "github.com/go-ozzo/ozzo-validation/v4" - "github.com/go-ozzo/ozzo-validation/v4/is" ) func ValidateSendMessage(request structs.SendMessageRequest) { err := validation.ValidateStruct(&request, - validation.Field(&request.Phone, validation.Required, is.Digit, validation.Length(10, 25)), + validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), validation.Field(&request.Message, validation.Required, validation.Length(1, 50)), ) @@ -22,7 +24,7 @@ func ValidateSendMessage(request structs.SendMessageRequest) { func ValidateSendImage(request structs.SendImageRequest) { err := validation.ValidateStruct(&request, - validation.Field(&request.Phone, validation.Required, is.Digit, validation.Length(10, 25)), + validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), validation.Field(&request.Caption, validation.When(true, validation.Length(1, 200))), validation.Field(&request.Image, validation.Required), ) @@ -44,12 +46,11 @@ func ValidateSendImage(request structs.SendImageRequest) { Message: "your image is not allowed. please use jpg/jpeg/png", }) } - } func ValidateSendFile(request structs.SendFileRequest) { err := validation.ValidateStruct(&request, - validation.Field(&request.Phone, validation.Required, is.Digit, validation.Length(10, 25)), + validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), validation.Field(&request.File, validation.Required), ) @@ -59,9 +60,42 @@ func ValidateSendFile(request structs.SendFileRequest) { }) } - if request.File.Size > 10240000 { // 10MB + if request.File.Size > config.WhatsappSettingMaxFileSize { // 10MB + maxSizeString := humanize.Bytes(uint64(config.WhatsappSettingMaxFileSize)) + panic(utils.ValidationError{ + Message: fmt.Sprintf("max file upload is %s, please upload in cloud and send via text if your file is higher than %s", maxSizeString, maxSizeString), + }) + } +} + +func ValidateSendVideo(request structs.SendVideoRequest) { + err := validation.ValidateStruct(&request, + validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), + validation.Field(&request.Video, validation.Required), + ) + + if err != nil { + panic(utils.ValidationError{ + Message: err.Error(), + }) + } + + availableMimes := map[string]bool{ + "video/mp4": true, + "video/x-matroska": true, + "video/avi": true, + } + + if !availableMimes[request.Video.Header.Get("Content-Type")] { + panic(utils.ValidationError{ + Message: "your video type is not allowed. please use mp4/mkv", + }) + } + + if request.Video.Size > config.WhatsappSettingMaxVideoSize { // 30MB + maxSizeString := humanize.Bytes(uint64(config.WhatsappSettingMaxVideoSize)) panic(utils.ValidationError{ - Message: "max file upload is 10MB, please upload in cloud and send via text if your file is higher than 10MB", + Message: fmt.Sprintf("max video upload is %s, please upload in cloud and send via text if your file is higher than %s", maxSizeString, maxSizeString), }) } } diff --git a/src/validations/send_validation_test.go b/src/validations/send_validation_test.go index 57d31bf..b4ff7d7 100644 --- a/src/validations/send_validation_test.go +++ b/src/validations/send_validation_test.go @@ -2,7 +2,6 @@ package validations import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/structs" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/stretchr/testify/assert" "testing" ) @@ -24,26 +23,6 @@ func TestValidateSendMessage(t *testing.T) { }}, err: nil, }, - { - name: "error invalid phone", - args: args{request: structs.SendMessageRequest{ - Phone: "some-random-phone", - Message: "Hello this is testing", - }}, - err: utils.ValidationError{ - Message: "phone: must contain digits only.", - }, - }, - { - name: "error invalid phone contains dash (-)", - args: args{request: structs.SendMessageRequest{ - Phone: "6289-748-291", - Message: "Hello this is testing", - }}, - err: utils.ValidationError{ - Message: "phone: must contain digits only.", - }, - }, } for _, tt := range tests { diff --git a/src/views/index.html b/src/views/index.html index 9ea83ea..080add7 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -27,11 +27,10 @@

[[ app_name ]]

Features

-
+
Login
-
App
Scan your QRCode and you can use all this API feature
@@ -40,7 +39,6 @@
Logout
-
App
Remove your login session in application
@@ -49,7 +47,6 @@
Reconnect
-
App
Reconnect to whatsapp server, please do this if your api doesn't work or your application is down or restart @@ -58,53 +55,55 @@
-
-
+
+
-
Send Message (Text) | Private Message
-
App
+
User Info
- Send any message to any whatsapp number + You can search someone user info by phone
-
+
-
Send Message (Image) | Private Message
-
App
+
User Avatar
- Send image with -
jpg/jpeg/png
- type + You can search someone avatar by phone
-
+
-
Send Message (File) | Private Message
-
App
+
User List Groups
- Send any file up to -
10MB
+ Display all groups you joined +
+
+
+
+
+
User Privacy Setting
+
+ Get your privacy settings
-
-
+
+
-
Send Message (Text) | Group
-
App
+ Private +
Send Message
Send any message to any whatsapp number
-
+
-
Send Message (Image) | Group
-
App
+ Private +
Send Image
Send image with
jpg/jpeg/png
@@ -112,52 +111,66 @@
-
+
-
Send Message (File) | Group
-
App
+ Private +
Send File
Send any file up to -
10MB
+
{{ .MaxFileSize }}
+
+
+
+
+
+ Private +
Send Video
+
+ Send video
mp4
up to +
{{ .MaxVideoSize }}
-
-
+
+
-
User Info
-
App
+ Group +
Send Message
- You can search someone user info by phone + Send any message to any whatsapp number
-
+
-
User Avatar
-
App
+ Group +
Send Image
- You can search someone avatar by phone + Send image with +
jpg/jpeg/png
+ type
-
+
-
User List Groups
-
App
+ Group +
Send File
- Display all groups you joined + Send any file up to +
{{ .MaxFileSize }}
-
+
-
User Privacy Setting
-
App
+ Group +
Send Video
- Get your privacy settings + Send video
mp4
up to +
{{ .MaxVideoSize }}
@@ -283,6 +296,37 @@
+ + +