Browse Source

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
pull/16/head
Aldino Kemal 4 years ago
committed by GitHub
parent
commit
9a301c6068
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      docker/golang.Dockerfile
  2. 108
      docs/openapi.yaml
  3. 42
      readme.md
  4. 2
      src/.air.toml
  5. 10
      src/cmd/root.go
  6. 15
      src/config/settings.go
  7. 33
      src/controllers/send_controller.go
  8. 1
      src/go.mod
  9. 2
      src/go.sum
  10. 4
      src/main.go
  11. 1
      src/services/send_service.go
  12. 113
      src/services/send_service_impl.go
  13. 0
      src/statics/qrcode/.gitignore
  14. 0
      src/statics/senditems/.gitignore
  15. 17
      src/structs/send_struct.go
  16. 12
      src/utils/general.go
  17. 48
      src/validations/send_validation.go
  18. 21
      src/validations/send_validation_test.go
  19. 199
      src/views/index.html

8
docker/golang.Dockerfile

@ -2,22 +2,22 @@
# STEP 1 build executable binary # STEP 1 build executable binary
############################ ############################
FROM golang:alpine AS builder 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 WORKDIR /whatsapp
COPY ./src . COPY ./src .
# Fetch dependencies. # Fetch dependencies.
RUN go mod download RUN go mod download
# Install pkger # 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. # Build the binary.
RUN go build -o /app/whatsapp
RUN pkger && go build -o /app/whatsapp
############################# #############################
## STEP 2 build a smaller image ## STEP 2 build a smaller image
############################# #############################
FROM alpine FROM alpine
RUN apk update && apk add --no-cache vips-dev
RUN apk update && apk add --no-cache vips-dev ffmpeg
WORKDIR /app WORKDIR /app
# Copy compiled from builder. # Copy compiled from builder.
COPY --from=builder /app/whatsapp /app/whatsapp COPY --from=builder /app/whatsapp /app/whatsapp

108
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.2.1
version: 2.3.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
@ -606,9 +606,6 @@ paths:
caption: caption:
type: string type: string
example: selamat malam example: selamat malam
view_once:
type: boolean
example: 'false'
file: file:
type: string type: string
format: binary format: binary
@ -689,3 +686,106 @@ paths:
code: 500 code: 500
message: you are not loggin message: you are not loggin
results: null 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

42
readme.md

@ -3,6 +3,7 @@
### Feature ### Feature
- Send whatsapp via http API, [docs/openapi.yml](./docs/openapi.yaml) for more details - Send whatsapp via http API, [docs/openapi.yml](./docs/openapi.yaml) for more details
- Compress image before send - Compress image before send
- Compress video before send
- Customizable port and debug mode - Customizable port and debug mode
- `--port 8000` - `--port 8000`
- `--debug true` - `--debug true`
@ -13,13 +14,16 @@
- Mac OS: - Mac OS:
- `brew install vips` - `brew install vips`
- `brew install ffmpeg`
- `export CGO_CFLAGS_ALLOW="-Xpreprocessor"` - `export CGO_CFLAGS_ALLOW="-Xpreprocessor"`
- Linux: - Linux:
- `sudo apt update` - `sudo apt update`
- `sudo apt install libvips-dev` - `sudo apt install libvips-dev`
- `sudo apt install ffmpeg`
- Windows (not recomended, prefer using [WSL](https://docs.microsoft.com/en-us/windows/wsl/install)): - 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 - 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 ### How to use
@ -55,7 +59,7 @@
7. open `http://localhost:3000` in browser 7. open `http://localhost:3000` in browser
### Production Mode (docker) ### 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) ### Production Mode (binary)
- download binary from [release](https://github.com/aldinokemal/go-whatsapp-web-multidevice/releases) - 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 ### Current API
You can check [docs/openapi.yml](./docs/openapi.yaml) for detail 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 | <table> <thead> <tr> <th>Param</th> <th>Type</th> <th>Type</th> <th>Example</th> </tr></thead> <tbody> <tr> <td>phone</td><td>string</td><td>querystring</td><td>6289685024099</td></tr></tbody></table> |
| ✅ | User Avatar | GET | /user/avatar | <table> <thead> <tr> <th>Param</th> <th>Type</th> <th>Type</th> <th>Example</th> </tr></thead> <tbody> <tr> <td>phone</td><td>string</td><td>querystring</td><td>6289685024099</td></tr></tbody></table> |
| ✅ | User My Group List | GET | /user/my/groups | |
| ✅ | User My Privacy Setting | GET | /user/my/privacy | |
| ✅ | Send Message (Text) | POST | /send/message | <table> <thead> <tr> <th>Param</th> <th>Data Type</th> <th>Type</th> <th>Example</th> </tr></thead> <tbody> <tr> <td>phone</td><td>string</td><td>form-data</td><td>6289685024099</td></tr><tr> <td>message</td><td>string</td><td>form-data</td><td>Hello guys this is testing</td></tr><tr> <td>type</td><td>string (user/group)</td><td>form-data</td><td>user</td></tr></tbody></table> |
| ✅ | Send Message (Image) | POST | /send/image | <table> <thead> <tr> <th>Param</th> <th>Type</th> <th>Type</th> <th>Example</th> </tr></thead> <tbody> <tr> <td>phone</td><td>string</td><td>form-data</td><td>6289685024099</td></tr><tr> <td>caption</td><td>string</td><td>form-data</td><td>Hello guys this is caption</td></tr><tr> <td>view_once</td><td>bool</td><td>form-data</td><td>false</td></tr><tr> <td>image</td><td>binary</td><td>form-data</td><td>image/jpg,image/jpeg,image/png</td></tr><tr> <td>type</td><td>string (user/group)</td><td>form-data</td><td>user</td></tr></tbody></table> |
| ✅ | Send Message (File) | POST | /send/file | <table><thead><tr><th>Param</th><th>Type</th><th>Type</th><th>Example</th></tr></thead><tbody><tr><td>phone</td><td>string</td><td>form-data</td><td>6289685024099</td></tr><tr><td>file</td><td>binary</td><td>form-data</td><td>any (max: 10MB)</td></tr><tr> <td>type</td><td>string (user/group)</td><td>form-data</td><td>user</td></tr></tbody></table> |
| ❌ | Send Message (Video) | POST | /send/video | <table><thead><tr><th>Param</th><th>Type</th><th>Type</th><th>Example</th></tr></thead><tbody><tr><td>phone</td><td>string</td><td>form-data</td><td>6289685024099</td></tr><tr><td>video</td><td>binary</td><td>form-data</td><td>mp4/avi/mkv</td></tr><tr> <td>type</td><td>string (user/group)</td><td>form-data</td><td>user</td></tr></tbody></table> |
| 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 ✅ = 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) 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) 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 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 ### Mac OS NOTE

2
src/.air.toml

@ -2,4 +2,4 @@ root = '.'
tmp_dir = "tmp" tmp_dir = "tmp"
[build] [build]
exclude_dir = ["statics"]
exclude_dir = ["statics", "views"]

10
src/cmd/root.go

@ -7,6 +7,7 @@ import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/middleware" "github.com/aldinokemal/go-whatsapp-web-multidevice/middleware"
"github.com/aldinokemal/go-whatsapp-web-multidevice/services" "github.com/aldinokemal/go-whatsapp-web-multidevice/services"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/dustin/go-humanize"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger" "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") engine := html.NewFileSystem(pkger.Dir("/views"), ".html")
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
Views: engine, Views: engine,
BodyLimit: 10 * 1024 * 1024,
BodyLimit: 50 * 1024 * 1024,
}) })
app.Static("/statics", "./statics") app.Static("/statics", "./statics")
app.Use(middleware.Recovery()) app.Use(middleware.Recovery())
@ -79,7 +80,12 @@ func runRest(cmd *cobra.Command, args []string) {
userController.Route(app) userController.Route(app)
app.Get("/", func(ctx *fiber.Ctx) error { 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) err = app.Listen(":" + config.AppPort)

15
src/config/settings.go

@ -3,14 +3,17 @@ package config
type Browser string type Browser string
var ( 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" DBName string = "hydrogenWaCli.db"
WhatsappLogLevel string = "ERROR"
WhatsappAutoReplyMessage string
WhatsappLogLevel string = "ERROR"
WhatsappAutoReplyMessage string
WhatsappSettingMaxFileSize int64 = 30000000 // 10MB
WhatsappSettingMaxVideoSize int64 = 30000000 // 30MB
) )

33
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/message", controller.SendText)
app.Post("/send/image", controller.SendImage) app.Post("/send/image", controller.SendImage)
app.Post("/send/file", controller.SendFile) app.Post("/send/file", controller.SendFile)
app.Post("/send/video", controller.SendVideo)
} }
func (controller *SendController) SendText(c *fiber.Ctx) error { func (controller *SendController) SendText(c *fiber.Ctx) error {
@ -93,7 +94,7 @@ func (controller *SendController) SendFile(c *fiber.Ctx) error {
} else { } else {
request.Phone = request.Phone + "@s.whatsapp.net" request.Phone = request.Phone + "@s.whatsapp.net"
} }
response, err := controller.Service.SendFile(c, request) response, err := controller.Service.SendFile(c, request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
@ -103,3 +104,33 @@ func (controller *SendController) SendFile(c *fiber.Ctx) error {
Results: response, 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,
})
}

1
src/go.mod

@ -3,6 +3,7 @@ module github.com/aldinokemal/go-whatsapp-web-multidevice
go 1.17 go 1.17
require ( require (
github.com/dustin/go-humanize v1.0.0
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/go-ozzo/ozzo-validation/v4 v4.3.0
github.com/gofiber/fiber/v2 v2.26.0 github.com/gofiber/fiber/v2 v2.26.0
github.com/gofiber/template v1.6.22 github.com/gofiber/template v1.6.22

2
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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.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= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=

4
src/main.go

@ -1,7 +1,3 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package main package main
import "github.com/aldinokemal/go-whatsapp-web-multidevice/cmd" import "github.com/aldinokemal/go-whatsapp-web-multidevice/cmd"

1
src/services/send_service.go

@ -9,4 +9,5 @@ type SendService interface {
SendText(c *fiber.Ctx, request structs.SendMessageRequest) (response structs.SendMessageResponse, err error) SendText(c *fiber.Ctx, request structs.SendMessageRequest) (response structs.SendMessageResponse, err error)
SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, 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) SendFile(c *fiber.Ctx, request structs.SendFileRequest) (response structs.SendFileResponse, err error)
SendVideo(c *fiber.Ctx, request structs.SendVideoRequest) (response structs.SendVideoResponse, err error)
} }

113
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/structs"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
fiberUtils "github.com/gofiber/fiber/v2/utils"
"github.com/h2non/bimg" "github.com/h2non/bimg"
"go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow"
waProto "go.mau.fi/whatsmeow/binary/proto" waProto "go.mau.fi/whatsmeow/binary/proto"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"net/http" "net/http"
"os" "os"
"os/exec"
) )
type SendServiceImpl struct { 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) { func (service SendServiceImpl) SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, err error) {
utils.MustLogin(service.WaCli) utils.MustLogin(service.WaCli)
// Resize image
// Save image to server
oriImagePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.Image.Filename) oriImagePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.Image.Filename)
err = c.SaveFile(request.Image, oriImagePath) err = c.SaveFile(request.Image, oriImagePath)
if err != nil { if err != nil {
return response, err return response, err
} }
// Resize image
openImageBuffer, err := bimg.Read(oriImagePath) 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 { if err != nil {
return response, err return response, err
} }
@ -63,7 +66,7 @@ func (service SendServiceImpl) SendImage(c *fiber.Ctx, request structs.SendImage
if err != nil { if err != nil {
return response, err return response, err
} }
// Send to WA server // Send to WA server
dataWaCaption := request.Caption dataWaCaption := request.Caption
dataWaRecipient, ok := utils.ParseJID(request.Phone) dataWaRecipient, ok := utils.ParseJID(request.Phone)
@ -96,7 +99,7 @@ func (service SendServiceImpl) SendImage(c *fiber.Ctx, request structs.SendImage
go func() { go func() {
errDelete := utils.RemoveFile(0, oriImagePath, newImagePath) errDelete := utils.RemoveFile(0, oriImagePath, newImagePath)
if errDelete != nil { if errDelete != nil {
fmt.Println(errDelete)
fmt.Println("error when deleting picture: ", errDelete)
} }
}() }()
if err != nil { 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) { func (service SendServiceImpl) SendFile(c *fiber.Ctx, request structs.SendFileRequest) (response structs.SendFileResponse, err error) {
utils.MustLogin(service.WaCli) utils.MustLogin(service.WaCli)
// Resize image
oriFilePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.File.Filename) oriFilePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.File.Filename)
err = c.SaveFile(request.File, oriFilePath) err = c.SaveFile(request.File, oriFilePath)
if err != nil { if err != nil {
@ -153,7 +155,106 @@ func (service SendServiceImpl) SendFile(c *fiber.Ctx, request structs.SendFileRe
if err != nil { if err != nil {
return response, err return response, err
} else { } 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 return response, nil
} }
} }

0
src/statics/images/qrcode/.gitignore → src/statics/qrcode/.gitignore

0
src/statics/images/senditems/.gitignore → src/statics/senditems/.gitignore

17
src/structs/send_struct.go

@ -24,7 +24,7 @@ type SendImageRequest struct {
Caption string `json:"caption" form:"caption"` Caption string `json:"caption" form:"caption"`
Image *multipart.FileHeader `json:"image" form:"image"` Image *multipart.FileHeader `json:"image" form:"image"`
ViewOnce bool `json:"view_once" form:"view_once"` ViewOnce bool `json:"view_once" form:"view_once"`
Type SendType `json:"type" form:"message"`
Type SendType `json:"type" form:"type"`
} }
type SendImageResponse struct { type SendImageResponse struct {
@ -34,9 +34,22 @@ type SendImageResponse struct {
type SendFileRequest struct { type SendFileRequest struct {
Phone string `json:"phone" form:"phone"` Phone string `json:"phone" form:"phone"`
File *multipart.FileHeader `json:"file" form:"file"` File *multipart.FileHeader `json:"file" form:"file"`
Type SendType `json:"type" form:"message"`
Type SendType `json:"type" form:"type"`
} }
type SendFileResponse struct { type SendFileResponse struct {
Status string `json:"status"` 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"`
}

12
src/utils/general.go

@ -13,8 +13,12 @@ func RemoveFile(delaySecond int, paths ...string) error {
} }
for _, path := range paths { for _, path := range paths {
err := os.Remove(path)
return err
if path != "" {
err := os.Remove(path)
if err != nil {
return err
}
}
} }
return nil return nil
} }
@ -24,7 +28,9 @@ func CreateFolder(folderPath ...string) error {
for _, folder := range folderPath { for _, folder := range folderPath {
newFolder := filepath.Join(".", folder) newFolder := filepath.Join(".", folder)
err := os.MkdirAll(newFolder, os.ModePerm) err := os.MkdirAll(newFolder, os.ModePerm)
return err
if err != nil {
return err
}
} }
return nil return nil
} }

48
src/validations/send_validation.go

@ -1,15 +1,17 @@
package validations package validations
import ( 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/structs"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/dustin/go-humanize"
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
) )
func ValidateSendMessage(request structs.SendMessageRequest) { func ValidateSendMessage(request structs.SendMessageRequest) {
err := validation.ValidateStruct(&request, 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)), validation.Field(&request.Message, validation.Required, validation.Length(1, 50)),
) )
@ -22,7 +24,7 @@ func ValidateSendMessage(request structs.SendMessageRequest) {
func ValidateSendImage(request structs.SendImageRequest) { func ValidateSendImage(request structs.SendImageRequest) {
err := validation.ValidateStruct(&request, 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.Caption, validation.When(true, validation.Length(1, 200))),
validation.Field(&request.Image, validation.Required), 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", Message: "your image is not allowed. please use jpg/jpeg/png",
}) })
} }
} }
func ValidateSendFile(request structs.SendFileRequest) { func ValidateSendFile(request structs.SendFileRequest) {
err := validation.ValidateStruct(&request, 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), 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{ 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),
}) })
} }
} }

21
src/validations/send_validation_test.go

@ -2,7 +2,6 @@ package validations
import ( import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs" "github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
) )
@ -24,26 +23,6 @@ func TestValidateSendMessage(t *testing.T) {
}}, }},
err: nil, 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 { for _, tt := range tests {

199
src/views/index.html

@ -27,11 +27,10 @@
<h1 class="ui dividing header">[[ app_name ]]</h1> <h1 class="ui dividing header">[[ app_name ]]</h1>
<h3 class="first">Features</h3> <h3 class="first">Features</h3>
<div class="ui three cards">
<div class="ui three column stackable grid cards">
<div class="green card" @click="loginModal" style="cursor: pointer"> <div class="green card" @click="loginModal" style="cursor: pointer">
<div class="content"> <div class="content">
<div class="header">Login</div> <div class="header">Login</div>
<div class="meta">App</div>
<div class="description"> <div class="description">
Scan your QRCode and you can use all this API feature Scan your QRCode and you can use all this API feature
</div> </div>
@ -40,7 +39,6 @@
<div class="green card" @click="logoutProcess" style="cursor: pointer"> <div class="green card" @click="logoutProcess" style="cursor: pointer">
<div class="content"> <div class="content">
<div class="header">Logout</div> <div class="header">Logout</div>
<div class="meta">App</div>
<div class="description"> <div class="description">
Remove your login session in application Remove your login session in application
</div> </div>
@ -49,7 +47,6 @@
<div class="green card" @click="reconnectProcess" style="cursor: pointer"> <div class="green card" @click="reconnectProcess" style="cursor: pointer">
<div class="content"> <div class="content">
<div class="header">Reconnect</div> <div class="header">Reconnect</div>
<div class="meta">App</div>
<div class="description"> <div class="description">
Reconnect to whatsapp server, please do this if your api doesn't work or your application is down or Reconnect to whatsapp server, please do this if your api doesn't work or your application is down or
restart restart
@ -58,53 +55,55 @@
</div> </div>
</div> </div>
<div class="ui three cards">
<div class="green card" @click="sendMessageModal('user')" style="cursor: pointer">
<div class="ui four column doubling grid cards">
<div class="green card" @click="infoModal" style="cursor: pointer;">
<div class="content"> <div class="content">
<div class="header">Send Message (Text) | Private Message</div>
<div class="meta">App</div>
<div class="header">User Info</div>
<div class="description"> <div class="description">
Send any message to any whatsapp number
You can search someone user info by phone
</div> </div>
</div> </div>
</div> </div>
<div class="green card" @click="sendImageModal('user')" style="cursor:pointer;">
<div class="green card" @click="avatarModal" style="cursor: pointer;">
<div class="content"> <div class="content">
<div class="header">Send Message (Image) | Private Message</div>
<div class="meta">App</div>
<div class="header">User Avatar</div>
<div class="description"> <div class="description">
Send image with
<div class="ui blue horizontal label">jpg/jpeg/png</div>
type
You can search someone avatar by phone
</div> </div>
</div> </div>
</div> </div>
<div class="green card" @click="sendFileModal('user')" style="cursor: pointer">
<div class="green card" @click="groupModal" style="cursor: pointer">
<div class="content"> <div class="content">
<div class="header">Send Message (File) | Private Message</div>
<div class="meta">App</div>
<div class="header">User List Groups</div>
<div class="description"> <div class="description">
Send any file up to
<div class="ui blue horizontal label">10MB</div>
Display all groups you joined
</div>
</div>
</div>
<div class="green card" @click="privacyModal" style="cursor: pointer">
<div class="content">
<div class="header">User Privacy Setting</div>
<div class="description">
Get your privacy settings
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="ui three cards">
<div class="green card" @click="sendMessageModal('group')" style="cursor: pointer">
<div class="ui four column doubling grid cards">
<div class="green card" @click="sendMessageModal('user')" style="cursor: pointer">
<div class="content"> <div class="content">
<div class="header">Send Message (Text) | Group</div>
<div class="meta">App</div>
<a class="ui blue right ribbon label">Private</a>
<div class="header">Send Message </div>
<div class="description"> <div class="description">
Send any message to any whatsapp number Send any message to any whatsapp number
</div> </div>
</div> </div>
</div> </div>
<div class="green card" @click="sendImageModal('group')" style="cursor:pointer;">
<div class="green card" @click="sendImageModal('user')" style="cursor:pointer;">
<div class="content"> <div class="content">
<div class="header">Send Message (Image) | Group</div>
<div class="meta">App</div>
<a class="ui blue right ribbon label">Private</a>
<div class="header">Send Image</div>
<div class="description"> <div class="description">
Send image with Send image with
<div class="ui blue horizontal label">jpg/jpeg/png</div> <div class="ui blue horizontal label">jpg/jpeg/png</div>
@ -112,52 +111,66 @@
</div> </div>
</div> </div>
</div> </div>
<div class="green card" @click="sendFileModal('group')" style="cursor: pointer">
<div class="green card" @click="sendFileModal('user')" style="cursor: pointer">
<div class="content"> <div class="content">
<div class="header">Send Message (File) | Group</div>
<div class="meta">App</div>
<a class="ui blue right ribbon label">Private</a>
<div class="header">Send File</div>
<div class="description"> <div class="description">
Send any file up to Send any file up to
<div class="ui blue horizontal label">10MB</div>
<div class="ui blue horizontal label">{{ .MaxFileSize }}</div>
</div>
</div>
</div>
<div class="green card" @click="sendVideoModal('user')" style="cursor: pointer">
<div class="content">
<a class="ui blue right ribbon label">Private</a>
<div class="header">Send Video</div>
<div class="description">
Send video <div class="ui blue horizontal label">mp4</div> up to
<div class="ui blue horizontal label">{{ .MaxVideoSize }}</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="ui four cards">
<div class="green card" @click="infoModal" style="cursor: pointer;">
<div class="ui four column doubling grid cards">
<div class="green card" @click="sendMessageModal('group')" style="cursor: pointer">
<div class="content"> <div class="content">
<div class="header">User Info</div>
<div class="meta">App</div>
<a class="ui teal right ribbon label">Group</a>
<div class="header">Send Message</div>
<div class="description"> <div class="description">
You can search someone user info by phone
Send any message to any whatsapp number
</div> </div>
</div> </div>
</div> </div>
<div class="green card" @click="avatarModal" style="cursor: pointer;">
<div class="green card" @click="sendImageModal('group')" style="cursor:pointer;">
<div class="content"> <div class="content">
<div class="header">User Avatar</div>
<div class="meta">App</div>
<a class="ui teal right ribbon label">Group</a>
<div class="header">Send Image</div>
<div class="description"> <div class="description">
You can search someone avatar by phone
Send image with
<div class="ui teal horizontal label">jpg/jpeg/png</div>
type
</div> </div>
</div> </div>
</div> </div>
<div class="green card" @click="groupModal" style="cursor: pointer">
<div class="green card" @click="sendFileModal('group')" style="cursor: pointer">
<div class="content"> <div class="content">
<div class="header">User List Groups</div>
<div class="meta">App</div>
<a class="ui teal right ribbon label">Group</a>
<div class="header">Send File</div>
<div class="description"> <div class="description">
Display all groups you joined
Send any file up to
<div class="ui teal horizontal label">{{ .MaxFileSize }}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="green card" @click="privacyModal" style="cursor: pointer">
<div class="green card" @click="sendVideoModal('group')" style="cursor: pointer">
<div class="content"> <div class="content">
<div class="header">User Privacy Setting</div>
<div class="meta">App</div>
<a class="ui teal right ribbon label">Group</a>
<div class="header">Send Video</div>
<div class="description"> <div class="description">
Get your privacy settings
Send video <div class="ui teal horizontal label">mp4</div> up to
<div class="ui teal horizontal label">{{ .MaxVideoSize }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -283,6 +296,37 @@
</div> </div>
</div> </div>
<!-- Modal SendFile -->
<div class="ui small modal" id="modalSendVideo">
<i class="close icon"></i>
<div class="header">
Send Video
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Phone / Group ID</label>
<input v-model="video_phone" type="text" placeholder="6289..."
aria-label="phone">
</div>
<div class="field" style="padding-bottom: 30px">
<label>Video</label>
<input type="file" class="inputfile" id="video_file" style="display: none" accept="video/*"/>
<label for="video_file" class="ui positive medium green left floated button" style="color: white">
<i class="ui upload icon"></i>
Upload video
</label>
</div>
</form>
</div>
<div class="actions">
<div class="ui positive right labeled icon button" @click="sendVideoProcess">
Send
<i class="send icon"></i>
</div>
</div>
</div>
<!-- Modal UserGroup --> <!-- Modal UserGroup -->
<div class="ui small modal" id="modalUserGroup"> <div class="ui small modal" id="modalUserGroup">
<i class="close icon"></i> <i class="close icon"></i>
@ -301,7 +345,7 @@
</thead> </thead>
<tbody v-if="data_groups != null"> <tbody v-if="data_groups != null">
<tr v-for="g in data_groups"> <tr v-for="g in data_groups">
<td data-label="GID">[[ g.JID.User ]]</td>
<td data-label="GID">[[ g.JID.split('@')[0] ]]</td>
<td data-label="Name">[[ g.Name ]]</td> <td data-label="Name">[[ g.Name ]]</td>
<td data-label="Age">[[ g.Participants.length ]]</td> <td data-label="Age">[[ g.Participants.length ]]</td>
<td data-label="Job">[[ g.GroupCreated ]]</td> <td data-label="Job">[[ g.GroupCreated ]]</td>
@ -666,6 +710,59 @@
} }
} }
const sendVideo = {
data() {
return {
video_type: 'user',
video_phone: '',
}
},
methods: {
sendVideoModal(type) {
this.video_type = type
$('#modalSendVideo').modal('show');
},
async sendVideoProcess() {
try {
let response = await this.sendVideoApi()
showSuccessInfo(response)
} catch (err) {
showErrorInfo(err)
}
},
sendVideoApi() {
return new Promise(async (resolve, reject) => {
try {
let payload = new FormData();
payload.append("phone", this.video_phone)
payload.append("video", $("#video_file")[0].files[0])
payload.append("type", this.video_type)
let response = await axios.post(`${this.app_host}/send/video`, payload, {
// Axios Bug, always content-type that make boundary not set default by browser https://github.com/axios/axios/issues/1603
transformRequest: (data, headers) => {
delete headers.post['Content-Type'];
return data;
}
})
this.sendVideoReset();
resolve(response.data.message)
} catch (error) {
if (error.response) {
reject(error.response.data.message)
} else {
reject(error.message)
}
}
})
},
sendVideoReset() {
this.video_phone = '';
this.video_type = 'user';
$("#video_file").val('');
},
}
}
const userGroups = { const userGroups = {
data() { data() {
return { return {
@ -843,10 +940,10 @@
data() { data() {
return { return {
app_host: {{ .AppHost }}, app_host: {{ .AppHost }},
app_name: 'Whatsapp API Multi Device App'
app_name: 'Whatsapp API Multi Device App ({{ .AppVersion }})'
} }
}, },
mixins: [login, logout, reconnect, sendMessage, sendImage, sendFile, userGroups, userPrivacy, userAvatar, userInfo]
mixins: [login, logout, reconnect, sendMessage, sendImage, sendFile, sendVideo, userGroups, userPrivacy, userAvatar, userInfo]
}).mount('#app') }).mount('#app')
</script> </script>
</body> </body>
Loading…
Cancel
Save