├── .dockerignore ├── .env.example ├── .gitignore ├── .goreleaser.yml ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── main │ └── main.go ├── dbs └── .gitkeep ├── docker-compose.yaml ├── docs ├── docs.go ├── swagger.json └── swagger.yaml ├── go.mod ├── go.sum ├── internal ├── auth │ ├── auth.go │ └── types │ │ ├── data.go │ │ ├── request.go │ │ └── response.go ├── index │ └── index.go ├── route.go ├── routines.go ├── startup.go └── whatsapp │ ├── types │ ├── request.go │ └── response.go │ └── whatsapp.go └── pkg ├── auth ├── auth.go └── basic.go ├── env └── env.go ├── log └── log.go ├── router ├── cache.go ├── handler.go ├── middleware.go ├── response.go └── router.go └── whatsapp ├── drivers.go ├── version.go └── whatsapp.go /.dockerignore: -------------------------------------------------------------------------------- 1 | dist 2 | vendor 3 | dbs/* 4 | .bin/ 5 | .env 6 | **/.DS_Store 7 | **/.gitkeep -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # ----------------------------------- 2 | # Server Configuration 3 | # ----------------------------------- 4 | SERVER_ADDRESS=0.0.0.0 5 | SERVER_PORT=3000 6 | 7 | # ----------------------------------- 8 | # HTTP Configuration 9 | # ----------------------------------- 10 | HTTP_BASE_URL=/api/v1/whatsapp 11 | 12 | # HTTP_CORS_ORIGIN=* 13 | # HTTP_BODY_LIMIT_SIZE=8m 14 | # HTTP_GZIP_LEVEL=1 15 | 16 | # HTTP_CACHE_CAPACITY=100 17 | # HTTP_CACHE_TTL_SECONDS=5 18 | 19 | # ----------------------------------- 20 | # Authentication Configuration 21 | # ----------------------------------- 22 | # AUTH_BASIC_USERNAME=ThisIsUsername 23 | AUTH_BASIC_PASSWORD=ThisIsPassword 24 | 25 | AUTH_JWT_SECRET=ThisIsJWTSecret 26 | AUTH_JWT_EXPIRED_HOUR=0 27 | 28 | # ----------------------------------- 29 | # WhatsApp Configuration 30 | # ----------------------------------- 31 | WHATSAPP_DATASTORE_TYPE=sqlite 32 | WHATSAPP_DATASTORE_URI=file:dbs/WhatsApp.db?_pragma=foreign_keys(1) 33 | 34 | WHATSAPP_CLIENT_PROXY_URL="" 35 | 36 | WHATSAPP_MEDIA_IMAGE_COMPRESSION=true 37 | WHATSAPP_MEDIA_IMAGE_CONVERT_WEBP=true 38 | 39 | # WHATSAPP_VERSION_MAJOR=2 40 | # WHATSAPP_VERSION_MINOR=3000 41 | # WHATSAPP_VERSION_PATCH=1019175440 42 | 43 | # ----------------------------------- 44 | # 3rd Party Configuration 45 | # ----------------------------------- 46 | LIBWEBP_VERSION=0.6.1 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | vendor 3 | dbs/* 4 | .bin/ 5 | .env 6 | *.DS_Store 7 | !*.gitkeep -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - make vendor 4 | builds: 5 | - main: ./cmd/main/main.go 6 | env: 7 | - CGO_ENABLED=0 8 | ldflags: 9 | - -s 10 | - -w 11 | goos: 12 | - darwin 13 | - linux 14 | - windows 15 | goarch: 16 | - 386 17 | - amd64 18 | - arm64 19 | archives: 20 | - replacements: 21 | darwin: macos 22 | linux: linux 23 | windows: windows 24 | 386: 32-bit 25 | amd64: 64-bit 26 | arm64: arm-64-bit 27 | format: zip 28 | files: 29 | - LICENSE 30 | - README.md 31 | - .env.example 32 | - dbs/.gitkeep 33 | checksum: 34 | name_template: 'checksums.txt' 35 | snapshot: 36 | name_template: "{{ .Version }}_{{ .ShortCommit }}" 37 | changelog: 38 | filters: 39 | exclude: 40 | - '^docs:' 41 | - '^test:' 42 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.inferGopath": false 3 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Builder Image 2 | # --------------------------------------------------- 3 | FROM golang:1.22-alpine AS go-builder 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY . ./ 8 | 9 | RUN go mod download \ 10 | && CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -a -o main cmd/main/main.go 11 | 12 | 13 | # Final Image 14 | # --------------------------------------------------- 15 | FROM dimaskiddo/alpine:base-glibc 16 | MAINTAINER Dimas Restu Hidayanto 17 | 18 | ARG SERVICE_NAME="go-whatsapp-multidevice-rest" 19 | 20 | ENV PATH $PATH:/usr/app/${SERVICE_NAME} 21 | 22 | WORKDIR /usr/app/${SERVICE_NAME} 23 | 24 | RUN mkdir -p {.bin/webp,dbs} \ 25 | && chmod 775 {.bin/webp,dbs} 26 | 27 | COPY --from=go-builder /usr/src/app/.env.example ./.env 28 | COPY --from=go-builder /usr/src/app/main ./main 29 | 30 | EXPOSE 3000 31 | 32 | VOLUME ["/usr/app/${SERVICE_NAME}/dbs"] 33 | CMD ["main"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2022 Dimas Restu Hidayanto 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_CGO_ENABLED := 0 2 | SERVICE_NAME := go-whatsapp-multidevice-rest 3 | SERVICE_PORT := 3000 4 | IMAGE_NAME := go-whatsapp-multidevice-rest 5 | IMAGE_TAG := latest 6 | REBASE_URL := "github.com/dimaskiddo/go-whatsapp-multidevice-rest" 7 | COMMIT_MSG := "update improvement" 8 | 9 | .PHONY: 10 | 11 | .SILENT: 12 | 13 | init: 14 | make clean 15 | GO111MODULE=on go mod init 16 | 17 | init-dist: 18 | mkdir -p dist 19 | 20 | vendor: 21 | make clean 22 | GO111MODULE=on go mod tidy 23 | GO111MODULE=on go mod vendor 24 | 25 | release: 26 | make vendor 27 | make clean-dist 28 | goreleaser release --parallelism 1 --snapshot --skip-publish --rm-dist 29 | echo "Release '$(SERVICE_NAME)' complete, please check dist directory." 30 | 31 | publish: 32 | make vendor 33 | make clean-dist 34 | GITHUB_TOKEN=$(GITHUB_TOKEN) goreleaser release --parallelism 1 --rm-dist 35 | echo "Publish '$(SERVICE_NAME)' complete, please check your repository releases." 36 | 37 | build: 38 | make vendor 39 | CGO_ENABLED=$(BUILD_CGO_ENABLED) go build -ldflags="-s -w" -a -o $(SERVICE_NAME) cmd/main/main.go 40 | echo "Build '$(SERVICE_NAME)' complete." 41 | 42 | run: 43 | make vendor 44 | go run cmd/main/*.go 45 | 46 | gen-docs: 47 | rm -rf docs/* 48 | swag init -g cmd/main/main.go --output docs 49 | 50 | clean-dbs: 51 | rm -f dbs/WhatsApp.db 52 | 53 | clean-dist: 54 | rm -rf dist 55 | 56 | clean-build: 57 | rm -f $(SERVICE_NAME) 58 | 59 | clean: 60 | make clean-dbs 61 | make clean-dist 62 | make clean-build 63 | rm -rf vendor 64 | 65 | commit: 66 | make vendor 67 | make clean 68 | git add . 69 | git commit -am $(COMMIT_MSG) 70 | 71 | rebase: 72 | rm -rf .git 73 | find . -type f -iname "*.go*" -exec sed -i '' -e "s%github.com/dimaskiddo/go-whatsapp-multidevice-rest%$(REBASE_URL)%g" {} \; 74 | git init 75 | git remote add origin https://$(REBASE_URL).git 76 | 77 | push: 78 | git push origin master 79 | 80 | pull: 81 | git pull origin master 82 | 83 | c-build: 84 | docker build -t $(IMAGE_NAME):$(IMAGE_TAG) --build-arg SERVICE_NAME=$(SERVICE_NAME) . 85 | 86 | c-run: 87 | docker run -d -p $(SERVICE_PORT):$(SERVICE_PORT) --name $(SERVICE_NAME) --rm $(IMAGE_NAME):$(IMAGE_TAG) 88 | make c-logs 89 | 90 | c-shell: 91 | docker exec -it $(SERVICE_NAME) bash 92 | 93 | c-stop: 94 | docker stop $(SERVICE_NAME) 95 | 96 | c-logs: 97 | docker logs $(SERVICE_NAME) 98 | 99 | c-push: 100 | docker push $(IMAGE_NAME):$(IMAGE_TAG) 101 | 102 | c-clean: 103 | docker rmi -f $(IMAGE_NAME):$(IMAGE_TAG) 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go WhatsApp Multi-Device Implementation in REST API 2 | 3 | This repository contains example of implementation [go.mau.fi/whatsmeow](https://go.mau.fi/whatsmeow/) package with Multi-Session/Account Support. This example is using a [labstack/echo](https://github.com/labstack/echo) version 4.x. 4 | 5 | ## Features 6 | 7 | - Multi-Session/Account Support 8 | - Multi-Device Support 9 | - WhatsApp Authentication (QR Code and Logout) 10 | - WhatsApp Messaging Send Text 11 | - WhatsApp Messaging Send Media (Document, Image, Audio, Video, Sticker) 12 | - WhatsApp Messaging Send Location 13 | - WhatsApp Messaging Send Contact 14 | - WhatsApp Messaging Send Link 15 | - And Much More ... 16 | 17 | ## Getting Started 18 | 19 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 20 | See deployment section for notes on how to deploy the project on a live system. 21 | 22 | ### Prerequisites 23 | 24 | Prequisites packages: 25 | * Go (Go Programming Language) 26 | * Swag (Go Annotations Converter to Swagger Documentation) 27 | * GoReleaser (Go Automated Binaries Build) 28 | * Make (Automated Execution using Makefile) 29 | 30 | Optional packages: 31 | * Docker (Application Containerization) 32 | 33 | ### Deployment 34 | 35 | #### **Using Container** 36 | 37 | 1) Install Docker CE based on the [manual documentation](https://docs.docker.com/desktop/) 38 | 39 | 2) Run the following command on your Terminal or PowerShell 40 | ```sh 41 | docker run -d \ 42 | -p 3000:3000 \ 43 | --name go-whatsapp-multidevice \ 44 | --rm dimaskiddo/go-whatsapp-multidevice-rest:latest 45 | ``` 46 | 47 | 3) Now it should be accessible in your machine by accessing `localhost:3000/api/v1/whatsapp` or `127.0.0.1:3000/api/v1/whatsapp` 48 | 49 | 4) Try to use integrated API docs that accesible in `localhost:3000/api/v1/whatsapp/docs/` or `127.0.0.1:3000/api/v1/whatsapp/docs/` 50 | 51 | #### **Using Pre-Build Binaries** 52 | 53 | 1) Download Pre-Build Binaries from the [release page](https://github.com/dimaskiddo/go-whatsapp-multidevice-rest/releases) 54 | 55 | 2) Extract the zipped file 56 | 57 | 3) Copy the `.env.default` file as `.env` file 58 | 59 | 4) Run the pre-build binary 60 | ```sh 61 | # MacOS / Linux 62 | chmod 755 go-whatsapp-multidevice-rest 63 | ./go-whatsapp-multidevice-rest 64 | 65 | # Windows 66 | # You can double click it or using PowerShell 67 | .\go-whatsapp-multidevice-rest.exe 68 | ``` 69 | 70 | 5) Now it should be accessible in your machine by accessing `localhost:3000/api/v1/whatsapp` or `127.0.0.1:3000/api/v1/whatsapp` 71 | 72 | 6) Try to use integrated API docs that accesible in `localhost:3000/api/v1/whatsapp/docs/` or `127.0.0.1:3000/api/v1/whatsapp/docs/` 73 | 74 | #### **Build From Source** 75 | 76 | Below is the instructions to make this source code running: 77 | 78 | 1) Create a Go Workspace directory and export it as the extended GOPATH directory 79 | ```sh 80 | cd 81 | export GOPATH=$GOPATH:"`pwd`" 82 | ``` 83 | 84 | 2) Under the Go Workspace directory create a source directory 85 | ```sh 86 | mkdir -p src/github.com/dimaskiddo/go-whatsapp-multidevice-rest 87 | ``` 88 | 89 | 3) Move to the created directory and pull codebase 90 | ```sh 91 | cd src/github.com/dimaskiddo/go-whatsapp-multidevice-rest 92 | git clone -b master https://github.com/dimaskiddo/go-whatsapp-multidevice-rest.git . 93 | ``` 94 | 95 | 4) Run following command to pull vendor packages 96 | ```sh 97 | make vendor 98 | ``` 99 | 100 | 5) Link or copy environment variables file 101 | ```sh 102 | ln -sf .env.development .env 103 | # - OR - 104 | cp .env.development .env 105 | ``` 106 | 107 | 6) Until this step you already can run this code by using this command 108 | ```sh 109 | make run 110 | ``` 111 | 112 | 7) *(Optional)* Use following command to build this code into binary spesific platform 113 | ```sh 114 | make build 115 | ``` 116 | 117 | 8) *(Optional)* To make mass binaries distribution you can use following command 118 | ```sh 119 | make release 120 | ``` 121 | 122 | 9) Now it should be accessible in your machine by accessing `localhost:3000/api/v1/whatsapp` or `127.0.0.1:3000/api/v1/whatsapp` 123 | 124 | 10) Try to use integrated API docs that accesible in `localhost:3000/api/v1/whatsapp/docs/` or `127.0.0.1:3000/api/v1/whatsapp/docs/` 125 | 126 | ## API Access 127 | 128 | You can access any endpoint under **HTTP_BASE_URL** environment variable which by default located at `.env` file. 129 | 130 | Integrated API Documentation can be accessed in `/docs/` or by default it's in `localhost:3000/api/v1/whatsapp/docs/` or `127.0.0.1:3000/api/v1/whatsapp/docs/` 131 | 132 | ## Running The Tests 133 | 134 | Currently the test is not ready yet :) 135 | 136 | ## Built With 137 | 138 | * [Go](https://golang.org/) - Go Programming Languange 139 | * [Swag](https://github.com/swaggo/swag) - Go Annotations Converter to Swagger Documentation 140 | * [GoReleaser](https://github.com/goreleaser/goreleaser) - Go Automated Binaries Build 141 | * [Make](https://www.gnu.org/software/make/) - GNU Make Automated Execution 142 | * [Docker](https://www.docker.com/) - Application Containerization 143 | 144 | ## Authors 145 | 146 | * **Dimas Restu Hidayanto** - *Initial Work* - [DimasKiddo](https://github.com/dimaskiddo) 147 | 148 | See also the list of [contributors](https://github.com/dimaskiddo/go-whatsapp-multidevice-rest/contributors) who participated in this project 149 | 150 | ## Annotation 151 | 152 | You can seek more information for the make command parameters in the [Makefile](https://github.com/dimaskiddo/go-whatsapp-multidevice-rest/-/raw/master/Makefile) 153 | 154 | ## License 155 | 156 | Copyright (C) 2022 Dimas Restu Hidayanto 157 | 158 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 159 | 160 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 161 | 162 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 163 | -------------------------------------------------------------------------------- /cmd/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // @title Go WhatsApp Multi-Device REST API 4 | // @version 1.x 5 | // @description This is WhatsApp Multi-Device Implementation in Go REST API 6 | 7 | // @contact.name Dimas Restu Hidayanto 8 | // @contact.url https://github.com/dimaskiddo 9 | // @contact.email drh.dimasrestu@gmail.com 10 | 11 | // @securityDefinitions.basic BasicAuth 12 | 13 | // @securityDefinitions.apikey BearerAuth 14 | // @in header 15 | // @name Authorization 16 | 17 | import ( 18 | "context" 19 | "net/http" 20 | "os" 21 | "os/signal" 22 | "strings" 23 | "syscall" 24 | "time" 25 | 26 | cron "github.com/robfig/cron/v3" 27 | 28 | "github.com/labstack/echo/v4" 29 | "github.com/labstack/echo/v4/middleware" 30 | 31 | "github.com/go-playground/validator/v10" 32 | 33 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/env" 34 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/log" 35 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" 36 | 37 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal" 38 | ) 39 | 40 | type Server struct { 41 | Address string 42 | Port string 43 | } 44 | 45 | type EchoValidator struct { 46 | Validator *validator.Validate 47 | } 48 | 49 | func (ev *EchoValidator) Validate(i interface{}) error { 50 | return ev.Validator.Struct(i) 51 | } 52 | 53 | func main() { 54 | var err error 55 | 56 | // Intialize Cron 57 | c := cron.New(cron.WithChain( 58 | cron.Recover(cron.DiscardLogger), 59 | ), cron.WithSeconds()) 60 | 61 | // Initialize Echo 62 | e := echo.New() 63 | 64 | // Router Recovery 65 | e.Use(middleware.Recover()) 66 | 67 | // Router Compression 68 | e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ 69 | Level: router.GZipLevel, 70 | Skipper: func(c echo.Context) bool { 71 | if strings.Contains(c.Request().URL.Path, "docs") { 72 | return true 73 | } 74 | 75 | return false 76 | }, 77 | })) 78 | 79 | // Router CORS 80 | e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 81 | AllowOrigins: []string{router.CORSOrigin}, 82 | AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization}, 83 | AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.PATCH, echo.DELETE}, 84 | })) 85 | 86 | // Router Security 87 | e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ 88 | ContentTypeNosniff: "nosniff", 89 | XSSProtection: "1; mode=block", 90 | XFrameOptions: "SAMEORIGIN", 91 | })) 92 | 93 | // Router Body Size Limit 94 | e.Use(middleware.BodyLimitWithConfig(middleware.BodyLimitConfig{ 95 | Limit: router.BodyLimit, 96 | })) 97 | 98 | // Router Cache 99 | e.Use(router.HttpCacheInMemory( 100 | router.CacheCapacity, 101 | router.CacheTTLSeconds, 102 | )) 103 | 104 | // Router RealIP 105 | e.Use(router.HttpRealIP()) 106 | 107 | // Router Validator 108 | e.Validator = &EchoValidator{ 109 | Validator: validator.New(), 110 | } 111 | 112 | // Router Default Handler 113 | e.HTTPErrorHandler = router.HttpErrorHandler 114 | e.GET("/favicon.ico", router.ResponseNoContent) 115 | 116 | // Load Internal Routes 117 | internal.Routes(e) 118 | 119 | // Running Startup Tasks 120 | internal.Startup() 121 | 122 | // Running Routines Tasks 123 | internal.Routines(c) 124 | 125 | // Get Server Configuration 126 | var serverConfig Server 127 | 128 | serverConfig.Address, err = env.GetEnvString("SERVER_ADDRESS") 129 | if err != nil { 130 | serverConfig.Address = "127.0.0.1" 131 | } 132 | 133 | serverConfig.Port, err = env.GetEnvString("SERVER_PORT") 134 | if err != nil { 135 | serverConfig.Port = "3000" 136 | } 137 | 138 | // Start Server 139 | go func() { 140 | err := e.Start(serverConfig.Address + ":" + serverConfig.Port) 141 | if err != nil && err != http.ErrServerClosed { 142 | log.Print(nil).Fatal(err.Error()) 143 | } 144 | }() 145 | 146 | // Watch for Shutdown Signal 147 | sigShutdown := make(chan os.Signal, 1) 148 | signal.Notify(sigShutdown, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) 149 | <-sigShutdown 150 | 151 | // Wait 5 Seconds Before Graceful Shutdown 152 | ctxShutdown, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second) 153 | defer cancelShutdown() 154 | 155 | // Try To Shutdown Server 156 | err = e.Shutdown(ctxShutdown) 157 | if err != nil { 158 | log.Print(nil).Fatal(err.Error()) 159 | } 160 | 161 | // Try To Shutdown Cron 162 | c.Stop() 163 | } 164 | -------------------------------------------------------------------------------- /dbs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimaskiddo/go-whatsapp-multidevice-rest/7ef920cc5c0cbda1cc6ecbfe03dccce3e55f6ddb/dbs/.gitkeep -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | networks: 4 | whatsapp: 5 | driver: bridge 6 | 7 | services: 8 | go-whatsapp-multidevice-rest: 9 | build: 10 | context: . 11 | dockerfile: ./Dockerfile 12 | image: 'dimaskiddo/go-whatsapp-multidevice-rest:latest' 13 | container_name: 'go-whatsapp-multidevice-rest' 14 | networks: 15 | - whatsapp 16 | ports: 17 | - 3000:3000 18 | env_file: 19 | - ./.env 20 | volumes: 21 | - ./dbs:/usr/app/go-whatsapp-multidevice-rest/dbs 22 | restart: unless-stopped 23 | -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs GENERATED BY SWAG; DO NOT EDIT 2 | // This file was generated by swaggo/swag 3 | package docs 4 | 5 | import "github.com/swaggo/swag" 6 | 7 | const docTemplate = `{ 8 | "schemes": {{ marshal .Schemes }}, 9 | "swagger": "2.0", 10 | "info": { 11 | "description": "{{escape .Description}}", 12 | "title": "{{.Title}}", 13 | "contact": { 14 | "name": "Dimas Restu Hidayanto", 15 | "url": "https://github.com/dimaskiddo", 16 | "email": "drh.dimasrestu@gmail.com" 17 | }, 18 | "version": "{{.Version}}" 19 | }, 20 | "host": "{{.Host}}", 21 | "basePath": "{{.BasePath}}", 22 | "paths": { 23 | "/": { 24 | "get": { 25 | "description": "Get The Server Status", 26 | "produces": [ 27 | "application/json" 28 | ], 29 | "tags": [ 30 | "Root" 31 | ], 32 | "summary": "Show The Status of The Server", 33 | "responses": { 34 | "200": { 35 | "description": "" 36 | } 37 | } 38 | } 39 | }, 40 | "/auth": { 41 | "get": { 42 | "security": [ 43 | { 44 | "BasicAuth": [] 45 | } 46 | ], 47 | "description": "Get Authentication Token", 48 | "produces": [ 49 | "application/json" 50 | ], 51 | "tags": [ 52 | "Root" 53 | ], 54 | "summary": "Generate Authentication Token", 55 | "responses": { 56 | "200": { 57 | "description": "" 58 | } 59 | } 60 | } 61 | }, 62 | "/group": { 63 | "get": { 64 | "security": [ 65 | { 66 | "BearerAuth": [] 67 | } 68 | ], 69 | "description": "Get Joined Groups Information from WhatsApp", 70 | "produces": [ 71 | "application/json" 72 | ], 73 | "tags": [ 74 | "WhatsApp Group" 75 | ], 76 | "summary": "Get Joined Groups Information", 77 | "responses": { 78 | "200": { 79 | "description": "" 80 | } 81 | } 82 | } 83 | }, 84 | "/group/join": { 85 | "post": { 86 | "security": [ 87 | { 88 | "BearerAuth": [] 89 | } 90 | ], 91 | "description": "Joining to Group From Invitation Link from WhatsApp", 92 | "produces": [ 93 | "application/json" 94 | ], 95 | "tags": [ 96 | "WhatsApp Group" 97 | ], 98 | "summary": "Join Group From Invitation Link", 99 | "parameters": [ 100 | { 101 | "type": "string", 102 | "description": "Group Invitation Link", 103 | "name": "link", 104 | "in": "formData", 105 | "required": true 106 | } 107 | ], 108 | "responses": { 109 | "200": { 110 | "description": "" 111 | } 112 | } 113 | } 114 | }, 115 | "/group/leave": { 116 | "post": { 117 | "security": [ 118 | { 119 | "BearerAuth": [] 120 | } 121 | ], 122 | "description": "Leaving Group By Group ID from WhatsApp", 123 | "produces": [ 124 | "application/json" 125 | ], 126 | "tags": [ 127 | "WhatsApp Group" 128 | ], 129 | "summary": "Leave Group By Group ID", 130 | "parameters": [ 131 | { 132 | "type": "string", 133 | "description": "Group ID", 134 | "name": "groupid", 135 | "in": "formData", 136 | "required": true 137 | } 138 | ], 139 | "responses": { 140 | "200": { 141 | "description": "" 142 | } 143 | } 144 | } 145 | }, 146 | "/login": { 147 | "post": { 148 | "security": [ 149 | { 150 | "BearerAuth": [] 151 | } 152 | ], 153 | "description": "Get QR Code for WhatsApp Multi-Device Login", 154 | "consumes": [ 155 | "multipart/form-data" 156 | ], 157 | "produces": [ 158 | "application/json", 159 | "text/html" 160 | ], 161 | "tags": [ 162 | "WhatsApp Authentication" 163 | ], 164 | "summary": "Generate QR Code for WhatsApp Multi-Device Login", 165 | "parameters": [ 166 | { 167 | "enum": [ 168 | "html", 169 | "json" 170 | ], 171 | "type": "string", 172 | "default": "html", 173 | "description": "Change Output Format in HTML or JSON", 174 | "name": "output", 175 | "in": "formData" 176 | } 177 | ], 178 | "responses": { 179 | "200": { 180 | "description": "" 181 | } 182 | } 183 | } 184 | }, 185 | "/login/pair": { 186 | "post": { 187 | "security": [ 188 | { 189 | "BearerAuth": [] 190 | } 191 | ], 192 | "description": "Get Pairing Code for WhatsApp Multi-Device Login", 193 | "consumes": [ 194 | "multipart/form-data" 195 | ], 196 | "produces": [ 197 | "application/json" 198 | ], 199 | "tags": [ 200 | "WhatsApp Authentication" 201 | ], 202 | "summary": "Pair Phone for WhatsApp Multi-Device Login", 203 | "responses": { 204 | "200": { 205 | "description": "" 206 | } 207 | } 208 | } 209 | }, 210 | "/logout": { 211 | "post": { 212 | "security": [ 213 | { 214 | "BearerAuth": [] 215 | } 216 | ], 217 | "description": "Make Device Logout from WhatsApp Multi-Device", 218 | "produces": [ 219 | "application/json" 220 | ], 221 | "tags": [ 222 | "WhatsApp Authentication" 223 | ], 224 | "summary": "Logout Device from WhatsApp Multi-Device", 225 | "responses": { 226 | "200": { 227 | "description": "" 228 | } 229 | } 230 | } 231 | }, 232 | "/message/delete": { 233 | "post": { 234 | "security": [ 235 | { 236 | "BearerAuth": [] 237 | } 238 | ], 239 | "description": "Delete Message to Spesific WhatsApp Personal ID or Group ID", 240 | "consumes": [ 241 | "multipart/form-data" 242 | ], 243 | "produces": [ 244 | "application/json" 245 | ], 246 | "tags": [ 247 | "WhatsApp Message" 248 | ], 249 | "summary": "Delete Message", 250 | "parameters": [ 251 | { 252 | "type": "string", 253 | "description": "Destination WhatsApp Personal ID or Group ID", 254 | "name": "msisdn", 255 | "in": "formData", 256 | "required": true 257 | }, 258 | { 259 | "type": "string", 260 | "description": "Message ID", 261 | "name": "messageid", 262 | "in": "formData", 263 | "required": true 264 | } 265 | ], 266 | "responses": { 267 | "200": { 268 | "description": "" 269 | } 270 | } 271 | } 272 | }, 273 | "/message/edit": { 274 | "post": { 275 | "security": [ 276 | { 277 | "BearerAuth": [] 278 | } 279 | ], 280 | "description": "Update Message to Spesific WhatsApp Personal ID or Group ID", 281 | "consumes": [ 282 | "multipart/form-data" 283 | ], 284 | "produces": [ 285 | "application/json" 286 | ], 287 | "tags": [ 288 | "WhatsApp Message" 289 | ], 290 | "summary": "Update Message", 291 | "parameters": [ 292 | { 293 | "type": "string", 294 | "description": "Destination WhatsApp Personal ID or Group ID", 295 | "name": "msisdn", 296 | "in": "formData", 297 | "required": true 298 | }, 299 | { 300 | "type": "string", 301 | "description": "Message ID", 302 | "name": "messageid", 303 | "in": "formData", 304 | "required": true 305 | }, 306 | { 307 | "type": "string", 308 | "description": "Text Message", 309 | "name": "message", 310 | "in": "formData", 311 | "required": true 312 | } 313 | ], 314 | "responses": { 315 | "200": { 316 | "description": "" 317 | } 318 | } 319 | } 320 | }, 321 | "/message/react": { 322 | "post": { 323 | "security": [ 324 | { 325 | "BearerAuth": [] 326 | } 327 | ], 328 | "description": "React Message to Spesific WhatsApp Personal ID or Group ID", 329 | "consumes": [ 330 | "multipart/form-data" 331 | ], 332 | "produces": [ 333 | "application/json" 334 | ], 335 | "tags": [ 336 | "WhatsApp Message" 337 | ], 338 | "summary": "React Message", 339 | "parameters": [ 340 | { 341 | "type": "string", 342 | "description": "Destination WhatsApp Personal ID or Group ID", 343 | "name": "msisdn", 344 | "in": "formData", 345 | "required": true 346 | }, 347 | { 348 | "type": "string", 349 | "description": "Message ID", 350 | "name": "messageid", 351 | "in": "formData", 352 | "required": true 353 | }, 354 | { 355 | "type": "string", 356 | "description": "Reaction Emoji", 357 | "name": "emoji", 358 | "in": "formData", 359 | "required": true 360 | } 361 | ], 362 | "responses": { 363 | "200": { 364 | "description": "" 365 | } 366 | } 367 | } 368 | }, 369 | "/registered": { 370 | "get": { 371 | "security": [ 372 | { 373 | "BearerAuth": [] 374 | } 375 | ], 376 | "description": "Check WhatsApp Personal ID is Registered", 377 | "produces": [ 378 | "application/json" 379 | ], 380 | "tags": [ 381 | "WhatsApp Information" 382 | ], 383 | "summary": "Check If WhatsApp Personal ID is Registered", 384 | "parameters": [ 385 | { 386 | "type": "string", 387 | "description": "WhatsApp Personal ID to Check", 388 | "name": "msisdn", 389 | "in": "query", 390 | "required": true 391 | } 392 | ], 393 | "responses": { 394 | "200": { 395 | "description": "" 396 | } 397 | } 398 | } 399 | }, 400 | "/send/audio": { 401 | "post": { 402 | "security": [ 403 | { 404 | "BearerAuth": [] 405 | } 406 | ], 407 | "description": "Send Audio Message to Spesific WhatsApp Personal ID or Group ID", 408 | "consumes": [ 409 | "multipart/form-data" 410 | ], 411 | "produces": [ 412 | "application/json" 413 | ], 414 | "tags": [ 415 | "WhatsApp Send Message" 416 | ], 417 | "summary": "Send Audio Message", 418 | "parameters": [ 419 | { 420 | "type": "string", 421 | "description": "Destination WhatsApp Personal ID or Group ID", 422 | "name": "msisdn", 423 | "in": "formData", 424 | "required": true 425 | }, 426 | { 427 | "type": "file", 428 | "description": "Audio File", 429 | "name": "audio", 430 | "in": "formData", 431 | "required": true 432 | } 433 | ], 434 | "responses": { 435 | "200": { 436 | "description": "" 437 | } 438 | } 439 | } 440 | }, 441 | "/send/contact": { 442 | "post": { 443 | "security": [ 444 | { 445 | "BearerAuth": [] 446 | } 447 | ], 448 | "description": "Send Contact Message to Spesific WhatsApp Personal ID or Group ID", 449 | "consumes": [ 450 | "multipart/form-data" 451 | ], 452 | "produces": [ 453 | "application/json" 454 | ], 455 | "tags": [ 456 | "WhatsApp Send Message" 457 | ], 458 | "summary": "Send Contact Message", 459 | "parameters": [ 460 | { 461 | "type": "string", 462 | "description": "Destination WhatsApp Personal ID or Group ID", 463 | "name": "msisdn", 464 | "in": "formData", 465 | "required": true 466 | }, 467 | { 468 | "type": "string", 469 | "description": "Contact Name", 470 | "name": "name", 471 | "in": "formData", 472 | "required": true 473 | }, 474 | { 475 | "type": "string", 476 | "description": "Contact Phone", 477 | "name": "phone", 478 | "in": "formData", 479 | "required": true 480 | } 481 | ], 482 | "responses": { 483 | "200": { 484 | "description": "" 485 | } 486 | } 487 | } 488 | }, 489 | "/send/document": { 490 | "post": { 491 | "security": [ 492 | { 493 | "BearerAuth": [] 494 | } 495 | ], 496 | "description": "Send Document Message to Spesific WhatsApp Personal ID or Group ID", 497 | "consumes": [ 498 | "multipart/form-data" 499 | ], 500 | "produces": [ 501 | "application/json" 502 | ], 503 | "tags": [ 504 | "WhatsApp Send Message" 505 | ], 506 | "summary": "Send Document Message", 507 | "parameters": [ 508 | { 509 | "type": "string", 510 | "description": "Destination WhatsApp Personal ID or Group ID", 511 | "name": "msisdn", 512 | "in": "formData", 513 | "required": true 514 | }, 515 | { 516 | "type": "file", 517 | "description": "Document File", 518 | "name": "document", 519 | "in": "formData", 520 | "required": true 521 | } 522 | ], 523 | "responses": { 524 | "200": { 525 | "description": "" 526 | } 527 | } 528 | } 529 | }, 530 | "/send/image": { 531 | "post": { 532 | "security": [ 533 | { 534 | "BearerAuth": [] 535 | } 536 | ], 537 | "description": "Send Image Message to Spesific WhatsApp Personal ID or Group ID", 538 | "consumes": [ 539 | "multipart/form-data" 540 | ], 541 | "produces": [ 542 | "application/json" 543 | ], 544 | "tags": [ 545 | "WhatsApp Send Message" 546 | ], 547 | "summary": "Send Image Message", 548 | "parameters": [ 549 | { 550 | "type": "string", 551 | "description": "Destination WhatsApp Personal ID or Group ID", 552 | "name": "msisdn", 553 | "in": "formData", 554 | "required": true 555 | }, 556 | { 557 | "type": "string", 558 | "description": "Caption Image Message", 559 | "name": "caption", 560 | "in": "formData", 561 | "required": true 562 | }, 563 | { 564 | "type": "file", 565 | "description": "Image File", 566 | "name": "image", 567 | "in": "formData", 568 | "required": true 569 | }, 570 | { 571 | "type": "boolean", 572 | "default": false, 573 | "description": "Is View Once", 574 | "name": "viewonce", 575 | "in": "formData" 576 | } 577 | ], 578 | "responses": { 579 | "200": { 580 | "description": "" 581 | } 582 | } 583 | } 584 | }, 585 | "/send/link": { 586 | "post": { 587 | "security": [ 588 | { 589 | "BearerAuth": [] 590 | } 591 | ], 592 | "description": "Send Link Message to Spesific WhatsApp Personal ID or Group ID", 593 | "consumes": [ 594 | "multipart/form-data" 595 | ], 596 | "produces": [ 597 | "application/json" 598 | ], 599 | "tags": [ 600 | "WhatsApp Send Message" 601 | ], 602 | "summary": "Send Link Message", 603 | "parameters": [ 604 | { 605 | "type": "string", 606 | "description": "Destination WhatsApp Personal ID or Group ID", 607 | "name": "msisdn", 608 | "in": "formData", 609 | "required": true 610 | }, 611 | { 612 | "type": "string", 613 | "description": "Link Caption", 614 | "name": "caption", 615 | "in": "formData" 616 | }, 617 | { 618 | "type": "string", 619 | "description": "Link URL", 620 | "name": "url", 621 | "in": "formData", 622 | "required": true 623 | } 624 | ], 625 | "responses": { 626 | "200": { 627 | "description": "" 628 | } 629 | } 630 | } 631 | }, 632 | "/send/location": { 633 | "post": { 634 | "security": [ 635 | { 636 | "BearerAuth": [] 637 | } 638 | ], 639 | "description": "Send Location Message to Spesific WhatsApp Personal ID or Group ID", 640 | "consumes": [ 641 | "multipart/form-data" 642 | ], 643 | "produces": [ 644 | "application/json" 645 | ], 646 | "tags": [ 647 | "WhatsApp Send Message" 648 | ], 649 | "summary": "Send Location Message", 650 | "parameters": [ 651 | { 652 | "type": "string", 653 | "description": "Destination WhatsApp Personal ID or Group ID", 654 | "name": "msisdn", 655 | "in": "formData", 656 | "required": true 657 | }, 658 | { 659 | "type": "number", 660 | "description": "Location Latitude", 661 | "name": "latitude", 662 | "in": "formData", 663 | "required": true 664 | }, 665 | { 666 | "type": "number", 667 | "description": "Location Longitude", 668 | "name": "longitude", 669 | "in": "formData", 670 | "required": true 671 | } 672 | ], 673 | "responses": { 674 | "200": { 675 | "description": "" 676 | } 677 | } 678 | } 679 | }, 680 | "/send/poll": { 681 | "post": { 682 | "security": [ 683 | { 684 | "BearerAuth": [] 685 | } 686 | ], 687 | "description": "Send Poll to Spesific WhatsApp Personal ID or Group ID", 688 | "consumes": [ 689 | "multipart/form-data" 690 | ], 691 | "produces": [ 692 | "application/json" 693 | ], 694 | "tags": [ 695 | "WhatsApp Send Message" 696 | ], 697 | "summary": "Send Poll", 698 | "parameters": [ 699 | { 700 | "type": "string", 701 | "description": "Destination WhatsApp Personal ID or Group ID", 702 | "name": "msisdn", 703 | "in": "formData", 704 | "required": true 705 | }, 706 | { 707 | "type": "string", 708 | "description": "Poll Question", 709 | "name": "question", 710 | "in": "formData", 711 | "required": true 712 | }, 713 | { 714 | "type": "string", 715 | "description": "Poll Options (Comma Seperated for New Options)", 716 | "name": "options", 717 | "in": "formData", 718 | "required": true 719 | }, 720 | { 721 | "type": "boolean", 722 | "default": false, 723 | "description": "Is Multiple Answer", 724 | "name": "multianswer", 725 | "in": "formData" 726 | } 727 | ], 728 | "responses": { 729 | "200": { 730 | "description": "" 731 | } 732 | } 733 | } 734 | }, 735 | "/send/sticker": { 736 | "post": { 737 | "security": [ 738 | { 739 | "BearerAuth": [] 740 | } 741 | ], 742 | "description": "Send Sticker Message to Spesific WhatsApp Personal ID or Group ID", 743 | "consumes": [ 744 | "multipart/form-data" 745 | ], 746 | "produces": [ 747 | "application/json" 748 | ], 749 | "tags": [ 750 | "WhatsApp Send Message" 751 | ], 752 | "summary": "Send Sticker Message", 753 | "parameters": [ 754 | { 755 | "type": "string", 756 | "description": "Destination WhatsApp Personal ID or Group ID", 757 | "name": "msisdn", 758 | "in": "formData", 759 | "required": true 760 | }, 761 | { 762 | "type": "file", 763 | "description": "Sticker File", 764 | "name": "sticker", 765 | "in": "formData", 766 | "required": true 767 | } 768 | ], 769 | "responses": { 770 | "200": { 771 | "description": "" 772 | } 773 | } 774 | } 775 | }, 776 | "/send/text": { 777 | "post": { 778 | "security": [ 779 | { 780 | "BearerAuth": [] 781 | } 782 | ], 783 | "description": "Send Text Message to Spesific WhatsApp Personal ID or Group ID", 784 | "consumes": [ 785 | "multipart/form-data" 786 | ], 787 | "produces": [ 788 | "application/json" 789 | ], 790 | "tags": [ 791 | "WhatsApp Send Message" 792 | ], 793 | "summary": "Send Text Message", 794 | "parameters": [ 795 | { 796 | "type": "string", 797 | "description": "Destination WhatsApp Personal ID or Group ID", 798 | "name": "msisdn", 799 | "in": "formData", 800 | "required": true 801 | }, 802 | { 803 | "type": "string", 804 | "description": "Text Message", 805 | "name": "message", 806 | "in": "formData", 807 | "required": true 808 | } 809 | ], 810 | "responses": { 811 | "200": { 812 | "description": "" 813 | } 814 | } 815 | } 816 | }, 817 | "/send/video": { 818 | "post": { 819 | "security": [ 820 | { 821 | "BearerAuth": [] 822 | } 823 | ], 824 | "description": "Send Video Message to Spesific WhatsApp Personal ID or Group ID", 825 | "consumes": [ 826 | "multipart/form-data" 827 | ], 828 | "produces": [ 829 | "application/json" 830 | ], 831 | "tags": [ 832 | "WhatsApp Send Message" 833 | ], 834 | "summary": "Send Video Message", 835 | "parameters": [ 836 | { 837 | "type": "string", 838 | "description": "Destination WhatsApp Personal ID or Group ID", 839 | "name": "msisdn", 840 | "in": "formData", 841 | "required": true 842 | }, 843 | { 844 | "type": "string", 845 | "description": "Caption Video Message", 846 | "name": "caption", 847 | "in": "formData", 848 | "required": true 849 | }, 850 | { 851 | "type": "file", 852 | "description": "Video File", 853 | "name": "video", 854 | "in": "formData", 855 | "required": true 856 | }, 857 | { 858 | "type": "boolean", 859 | "default": false, 860 | "description": "Is View Once", 861 | "name": "viewonce", 862 | "in": "formData" 863 | } 864 | ], 865 | "responses": { 866 | "200": { 867 | "description": "" 868 | } 869 | } 870 | } 871 | } 872 | }, 873 | "securityDefinitions": { 874 | "BasicAuth": { 875 | "type": "basic" 876 | }, 877 | "BearerAuth": { 878 | "type": "apiKey", 879 | "name": "Authorization", 880 | "in": "header" 881 | } 882 | } 883 | }` 884 | 885 | // SwaggerInfo holds exported Swagger Info so clients can modify it 886 | var SwaggerInfo = &swag.Spec{ 887 | Version: "1.x", 888 | Host: "", 889 | BasePath: "", 890 | Schemes: []string{}, 891 | Title: "Go WhatsApp Multi-Device REST API", 892 | Description: "This is WhatsApp Multi-Device Implementation in Go REST API", 893 | InfoInstanceName: "swagger", 894 | SwaggerTemplate: docTemplate, 895 | } 896 | 897 | func init() { 898 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 899 | } 900 | -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "This is WhatsApp Multi-Device Implementation in Go REST API", 5 | "title": "Go WhatsApp Multi-Device REST API", 6 | "contact": { 7 | "name": "Dimas Restu Hidayanto", 8 | "url": "https://github.com/dimaskiddo", 9 | "email": "drh.dimasrestu@gmail.com" 10 | }, 11 | "version": "1.x" 12 | }, 13 | "paths": { 14 | "/": { 15 | "get": { 16 | "description": "Get The Server Status", 17 | "produces": [ 18 | "application/json" 19 | ], 20 | "tags": [ 21 | "Root" 22 | ], 23 | "summary": "Show The Status of The Server", 24 | "responses": { 25 | "200": { 26 | "description": "" 27 | } 28 | } 29 | } 30 | }, 31 | "/auth": { 32 | "get": { 33 | "security": [ 34 | { 35 | "BasicAuth": [] 36 | } 37 | ], 38 | "description": "Get Authentication Token", 39 | "produces": [ 40 | "application/json" 41 | ], 42 | "tags": [ 43 | "Root" 44 | ], 45 | "summary": "Generate Authentication Token", 46 | "responses": { 47 | "200": { 48 | "description": "" 49 | } 50 | } 51 | } 52 | }, 53 | "/group": { 54 | "get": { 55 | "security": [ 56 | { 57 | "BearerAuth": [] 58 | } 59 | ], 60 | "description": "Get Joined Groups Information from WhatsApp", 61 | "produces": [ 62 | "application/json" 63 | ], 64 | "tags": [ 65 | "WhatsApp Group" 66 | ], 67 | "summary": "Get Joined Groups Information", 68 | "responses": { 69 | "200": { 70 | "description": "" 71 | } 72 | } 73 | } 74 | }, 75 | "/group/join": { 76 | "post": { 77 | "security": [ 78 | { 79 | "BearerAuth": [] 80 | } 81 | ], 82 | "description": "Joining to Group From Invitation Link from WhatsApp", 83 | "produces": [ 84 | "application/json" 85 | ], 86 | "tags": [ 87 | "WhatsApp Group" 88 | ], 89 | "summary": "Join Group From Invitation Link", 90 | "parameters": [ 91 | { 92 | "type": "string", 93 | "description": "Group Invitation Link", 94 | "name": "link", 95 | "in": "formData", 96 | "required": true 97 | } 98 | ], 99 | "responses": { 100 | "200": { 101 | "description": "" 102 | } 103 | } 104 | } 105 | }, 106 | "/group/leave": { 107 | "post": { 108 | "security": [ 109 | { 110 | "BearerAuth": [] 111 | } 112 | ], 113 | "description": "Leaving Group By Group ID from WhatsApp", 114 | "produces": [ 115 | "application/json" 116 | ], 117 | "tags": [ 118 | "WhatsApp Group" 119 | ], 120 | "summary": "Leave Group By Group ID", 121 | "parameters": [ 122 | { 123 | "type": "string", 124 | "description": "Group ID", 125 | "name": "groupid", 126 | "in": "formData", 127 | "required": true 128 | } 129 | ], 130 | "responses": { 131 | "200": { 132 | "description": "" 133 | } 134 | } 135 | } 136 | }, 137 | "/login": { 138 | "post": { 139 | "security": [ 140 | { 141 | "BearerAuth": [] 142 | } 143 | ], 144 | "description": "Get QR Code for WhatsApp Multi-Device Login", 145 | "consumes": [ 146 | "multipart/form-data" 147 | ], 148 | "produces": [ 149 | "application/json", 150 | "text/html" 151 | ], 152 | "tags": [ 153 | "WhatsApp Authentication" 154 | ], 155 | "summary": "Generate QR Code for WhatsApp Multi-Device Login", 156 | "parameters": [ 157 | { 158 | "enum": [ 159 | "html", 160 | "json" 161 | ], 162 | "type": "string", 163 | "default": "html", 164 | "description": "Change Output Format in HTML or JSON", 165 | "name": "output", 166 | "in": "formData" 167 | } 168 | ], 169 | "responses": { 170 | "200": { 171 | "description": "" 172 | } 173 | } 174 | } 175 | }, 176 | "/login/pair": { 177 | "post": { 178 | "security": [ 179 | { 180 | "BearerAuth": [] 181 | } 182 | ], 183 | "description": "Get Pairing Code for WhatsApp Multi-Device Login", 184 | "consumes": [ 185 | "multipart/form-data" 186 | ], 187 | "produces": [ 188 | "application/json" 189 | ], 190 | "tags": [ 191 | "WhatsApp Authentication" 192 | ], 193 | "summary": "Pair Phone for WhatsApp Multi-Device Login", 194 | "responses": { 195 | "200": { 196 | "description": "" 197 | } 198 | } 199 | } 200 | }, 201 | "/logout": { 202 | "post": { 203 | "security": [ 204 | { 205 | "BearerAuth": [] 206 | } 207 | ], 208 | "description": "Make Device Logout from WhatsApp Multi-Device", 209 | "produces": [ 210 | "application/json" 211 | ], 212 | "tags": [ 213 | "WhatsApp Authentication" 214 | ], 215 | "summary": "Logout Device from WhatsApp Multi-Device", 216 | "responses": { 217 | "200": { 218 | "description": "" 219 | } 220 | } 221 | } 222 | }, 223 | "/message/delete": { 224 | "post": { 225 | "security": [ 226 | { 227 | "BearerAuth": [] 228 | } 229 | ], 230 | "description": "Delete Message to Spesific WhatsApp Personal ID or Group ID", 231 | "consumes": [ 232 | "multipart/form-data" 233 | ], 234 | "produces": [ 235 | "application/json" 236 | ], 237 | "tags": [ 238 | "WhatsApp Message" 239 | ], 240 | "summary": "Delete Message", 241 | "parameters": [ 242 | { 243 | "type": "string", 244 | "description": "Destination WhatsApp Personal ID or Group ID", 245 | "name": "msisdn", 246 | "in": "formData", 247 | "required": true 248 | }, 249 | { 250 | "type": "string", 251 | "description": "Message ID", 252 | "name": "messageid", 253 | "in": "formData", 254 | "required": true 255 | } 256 | ], 257 | "responses": { 258 | "200": { 259 | "description": "" 260 | } 261 | } 262 | } 263 | }, 264 | "/message/edit": { 265 | "post": { 266 | "security": [ 267 | { 268 | "BearerAuth": [] 269 | } 270 | ], 271 | "description": "Update Message to Spesific WhatsApp Personal ID or Group ID", 272 | "consumes": [ 273 | "multipart/form-data" 274 | ], 275 | "produces": [ 276 | "application/json" 277 | ], 278 | "tags": [ 279 | "WhatsApp Message" 280 | ], 281 | "summary": "Update Message", 282 | "parameters": [ 283 | { 284 | "type": "string", 285 | "description": "Destination WhatsApp Personal ID or Group ID", 286 | "name": "msisdn", 287 | "in": "formData", 288 | "required": true 289 | }, 290 | { 291 | "type": "string", 292 | "description": "Message ID", 293 | "name": "messageid", 294 | "in": "formData", 295 | "required": true 296 | }, 297 | { 298 | "type": "string", 299 | "description": "Text Message", 300 | "name": "message", 301 | "in": "formData", 302 | "required": true 303 | } 304 | ], 305 | "responses": { 306 | "200": { 307 | "description": "" 308 | } 309 | } 310 | } 311 | }, 312 | "/message/react": { 313 | "post": { 314 | "security": [ 315 | { 316 | "BearerAuth": [] 317 | } 318 | ], 319 | "description": "React Message to Spesific WhatsApp Personal ID or Group ID", 320 | "consumes": [ 321 | "multipart/form-data" 322 | ], 323 | "produces": [ 324 | "application/json" 325 | ], 326 | "tags": [ 327 | "WhatsApp Message" 328 | ], 329 | "summary": "React Message", 330 | "parameters": [ 331 | { 332 | "type": "string", 333 | "description": "Destination WhatsApp Personal ID or Group ID", 334 | "name": "msisdn", 335 | "in": "formData", 336 | "required": true 337 | }, 338 | { 339 | "type": "string", 340 | "description": "Message ID", 341 | "name": "messageid", 342 | "in": "formData", 343 | "required": true 344 | }, 345 | { 346 | "type": "string", 347 | "description": "Reaction Emoji", 348 | "name": "emoji", 349 | "in": "formData", 350 | "required": true 351 | } 352 | ], 353 | "responses": { 354 | "200": { 355 | "description": "" 356 | } 357 | } 358 | } 359 | }, 360 | "/registered": { 361 | "get": { 362 | "security": [ 363 | { 364 | "BearerAuth": [] 365 | } 366 | ], 367 | "description": "Check WhatsApp Personal ID is Registered", 368 | "produces": [ 369 | "application/json" 370 | ], 371 | "tags": [ 372 | "WhatsApp Information" 373 | ], 374 | "summary": "Check If WhatsApp Personal ID is Registered", 375 | "parameters": [ 376 | { 377 | "type": "string", 378 | "description": "WhatsApp Personal ID to Check", 379 | "name": "msisdn", 380 | "in": "query", 381 | "required": true 382 | } 383 | ], 384 | "responses": { 385 | "200": { 386 | "description": "" 387 | } 388 | } 389 | } 390 | }, 391 | "/send/audio": { 392 | "post": { 393 | "security": [ 394 | { 395 | "BearerAuth": [] 396 | } 397 | ], 398 | "description": "Send Audio Message to Spesific WhatsApp Personal ID or Group ID", 399 | "consumes": [ 400 | "multipart/form-data" 401 | ], 402 | "produces": [ 403 | "application/json" 404 | ], 405 | "tags": [ 406 | "WhatsApp Send Message" 407 | ], 408 | "summary": "Send Audio Message", 409 | "parameters": [ 410 | { 411 | "type": "string", 412 | "description": "Destination WhatsApp Personal ID or Group ID", 413 | "name": "msisdn", 414 | "in": "formData", 415 | "required": true 416 | }, 417 | { 418 | "type": "file", 419 | "description": "Audio File", 420 | "name": "audio", 421 | "in": "formData", 422 | "required": true 423 | } 424 | ], 425 | "responses": { 426 | "200": { 427 | "description": "" 428 | } 429 | } 430 | } 431 | }, 432 | "/send/contact": { 433 | "post": { 434 | "security": [ 435 | { 436 | "BearerAuth": [] 437 | } 438 | ], 439 | "description": "Send Contact Message to Spesific WhatsApp Personal ID or Group ID", 440 | "consumes": [ 441 | "multipart/form-data" 442 | ], 443 | "produces": [ 444 | "application/json" 445 | ], 446 | "tags": [ 447 | "WhatsApp Send Message" 448 | ], 449 | "summary": "Send Contact Message", 450 | "parameters": [ 451 | { 452 | "type": "string", 453 | "description": "Destination WhatsApp Personal ID or Group ID", 454 | "name": "msisdn", 455 | "in": "formData", 456 | "required": true 457 | }, 458 | { 459 | "type": "string", 460 | "description": "Contact Name", 461 | "name": "name", 462 | "in": "formData", 463 | "required": true 464 | }, 465 | { 466 | "type": "string", 467 | "description": "Contact Phone", 468 | "name": "phone", 469 | "in": "formData", 470 | "required": true 471 | } 472 | ], 473 | "responses": { 474 | "200": { 475 | "description": "" 476 | } 477 | } 478 | } 479 | }, 480 | "/send/document": { 481 | "post": { 482 | "security": [ 483 | { 484 | "BearerAuth": [] 485 | } 486 | ], 487 | "description": "Send Document Message to Spesific WhatsApp Personal ID or Group ID", 488 | "consumes": [ 489 | "multipart/form-data" 490 | ], 491 | "produces": [ 492 | "application/json" 493 | ], 494 | "tags": [ 495 | "WhatsApp Send Message" 496 | ], 497 | "summary": "Send Document Message", 498 | "parameters": [ 499 | { 500 | "type": "string", 501 | "description": "Destination WhatsApp Personal ID or Group ID", 502 | "name": "msisdn", 503 | "in": "formData", 504 | "required": true 505 | }, 506 | { 507 | "type": "file", 508 | "description": "Document File", 509 | "name": "document", 510 | "in": "formData", 511 | "required": true 512 | } 513 | ], 514 | "responses": { 515 | "200": { 516 | "description": "" 517 | } 518 | } 519 | } 520 | }, 521 | "/send/image": { 522 | "post": { 523 | "security": [ 524 | { 525 | "BearerAuth": [] 526 | } 527 | ], 528 | "description": "Send Image Message to Spesific WhatsApp Personal ID or Group ID", 529 | "consumes": [ 530 | "multipart/form-data" 531 | ], 532 | "produces": [ 533 | "application/json" 534 | ], 535 | "tags": [ 536 | "WhatsApp Send Message" 537 | ], 538 | "summary": "Send Image Message", 539 | "parameters": [ 540 | { 541 | "type": "string", 542 | "description": "Destination WhatsApp Personal ID or Group ID", 543 | "name": "msisdn", 544 | "in": "formData", 545 | "required": true 546 | }, 547 | { 548 | "type": "string", 549 | "description": "Caption Image Message", 550 | "name": "caption", 551 | "in": "formData", 552 | "required": true 553 | }, 554 | { 555 | "type": "file", 556 | "description": "Image File", 557 | "name": "image", 558 | "in": "formData", 559 | "required": true 560 | }, 561 | { 562 | "type": "boolean", 563 | "default": false, 564 | "description": "Is View Once", 565 | "name": "viewonce", 566 | "in": "formData" 567 | } 568 | ], 569 | "responses": { 570 | "200": { 571 | "description": "" 572 | } 573 | } 574 | } 575 | }, 576 | "/send/link": { 577 | "post": { 578 | "security": [ 579 | { 580 | "BearerAuth": [] 581 | } 582 | ], 583 | "description": "Send Link Message to Spesific WhatsApp Personal ID or Group ID", 584 | "consumes": [ 585 | "multipart/form-data" 586 | ], 587 | "produces": [ 588 | "application/json" 589 | ], 590 | "tags": [ 591 | "WhatsApp Send Message" 592 | ], 593 | "summary": "Send Link Message", 594 | "parameters": [ 595 | { 596 | "type": "string", 597 | "description": "Destination WhatsApp Personal ID or Group ID", 598 | "name": "msisdn", 599 | "in": "formData", 600 | "required": true 601 | }, 602 | { 603 | "type": "string", 604 | "description": "Link Caption", 605 | "name": "caption", 606 | "in": "formData" 607 | }, 608 | { 609 | "type": "string", 610 | "description": "Link URL", 611 | "name": "url", 612 | "in": "formData", 613 | "required": true 614 | } 615 | ], 616 | "responses": { 617 | "200": { 618 | "description": "" 619 | } 620 | } 621 | } 622 | }, 623 | "/send/location": { 624 | "post": { 625 | "security": [ 626 | { 627 | "BearerAuth": [] 628 | } 629 | ], 630 | "description": "Send Location Message to Spesific WhatsApp Personal ID or Group ID", 631 | "consumes": [ 632 | "multipart/form-data" 633 | ], 634 | "produces": [ 635 | "application/json" 636 | ], 637 | "tags": [ 638 | "WhatsApp Send Message" 639 | ], 640 | "summary": "Send Location Message", 641 | "parameters": [ 642 | { 643 | "type": "string", 644 | "description": "Destination WhatsApp Personal ID or Group ID", 645 | "name": "msisdn", 646 | "in": "formData", 647 | "required": true 648 | }, 649 | { 650 | "type": "number", 651 | "description": "Location Latitude", 652 | "name": "latitude", 653 | "in": "formData", 654 | "required": true 655 | }, 656 | { 657 | "type": "number", 658 | "description": "Location Longitude", 659 | "name": "longitude", 660 | "in": "formData", 661 | "required": true 662 | } 663 | ], 664 | "responses": { 665 | "200": { 666 | "description": "" 667 | } 668 | } 669 | } 670 | }, 671 | "/send/poll": { 672 | "post": { 673 | "security": [ 674 | { 675 | "BearerAuth": [] 676 | } 677 | ], 678 | "description": "Send Poll to Spesific WhatsApp Personal ID or Group ID", 679 | "consumes": [ 680 | "multipart/form-data" 681 | ], 682 | "produces": [ 683 | "application/json" 684 | ], 685 | "tags": [ 686 | "WhatsApp Send Message" 687 | ], 688 | "summary": "Send Poll", 689 | "parameters": [ 690 | { 691 | "type": "string", 692 | "description": "Destination WhatsApp Personal ID or Group ID", 693 | "name": "msisdn", 694 | "in": "formData", 695 | "required": true 696 | }, 697 | { 698 | "type": "string", 699 | "description": "Poll Question", 700 | "name": "question", 701 | "in": "formData", 702 | "required": true 703 | }, 704 | { 705 | "type": "string", 706 | "description": "Poll Options (Comma Seperated for New Options)", 707 | "name": "options", 708 | "in": "formData", 709 | "required": true 710 | }, 711 | { 712 | "type": "boolean", 713 | "default": false, 714 | "description": "Is Multiple Answer", 715 | "name": "multianswer", 716 | "in": "formData" 717 | } 718 | ], 719 | "responses": { 720 | "200": { 721 | "description": "" 722 | } 723 | } 724 | } 725 | }, 726 | "/send/sticker": { 727 | "post": { 728 | "security": [ 729 | { 730 | "BearerAuth": [] 731 | } 732 | ], 733 | "description": "Send Sticker Message to Spesific WhatsApp Personal ID or Group ID", 734 | "consumes": [ 735 | "multipart/form-data" 736 | ], 737 | "produces": [ 738 | "application/json" 739 | ], 740 | "tags": [ 741 | "WhatsApp Send Message" 742 | ], 743 | "summary": "Send Sticker Message", 744 | "parameters": [ 745 | { 746 | "type": "string", 747 | "description": "Destination WhatsApp Personal ID or Group ID", 748 | "name": "msisdn", 749 | "in": "formData", 750 | "required": true 751 | }, 752 | { 753 | "type": "file", 754 | "description": "Sticker File", 755 | "name": "sticker", 756 | "in": "formData", 757 | "required": true 758 | } 759 | ], 760 | "responses": { 761 | "200": { 762 | "description": "" 763 | } 764 | } 765 | } 766 | }, 767 | "/send/text": { 768 | "post": { 769 | "security": [ 770 | { 771 | "BearerAuth": [] 772 | } 773 | ], 774 | "description": "Send Text Message to Spesific WhatsApp Personal ID or Group ID", 775 | "consumes": [ 776 | "multipart/form-data" 777 | ], 778 | "produces": [ 779 | "application/json" 780 | ], 781 | "tags": [ 782 | "WhatsApp Send Message" 783 | ], 784 | "summary": "Send Text Message", 785 | "parameters": [ 786 | { 787 | "type": "string", 788 | "description": "Destination WhatsApp Personal ID or Group ID", 789 | "name": "msisdn", 790 | "in": "formData", 791 | "required": true 792 | }, 793 | { 794 | "type": "string", 795 | "description": "Text Message", 796 | "name": "message", 797 | "in": "formData", 798 | "required": true 799 | } 800 | ], 801 | "responses": { 802 | "200": { 803 | "description": "" 804 | } 805 | } 806 | } 807 | }, 808 | "/send/video": { 809 | "post": { 810 | "security": [ 811 | { 812 | "BearerAuth": [] 813 | } 814 | ], 815 | "description": "Send Video Message to Spesific WhatsApp Personal ID or Group ID", 816 | "consumes": [ 817 | "multipart/form-data" 818 | ], 819 | "produces": [ 820 | "application/json" 821 | ], 822 | "tags": [ 823 | "WhatsApp Send Message" 824 | ], 825 | "summary": "Send Video Message", 826 | "parameters": [ 827 | { 828 | "type": "string", 829 | "description": "Destination WhatsApp Personal ID or Group ID", 830 | "name": "msisdn", 831 | "in": "formData", 832 | "required": true 833 | }, 834 | { 835 | "type": "string", 836 | "description": "Caption Video Message", 837 | "name": "caption", 838 | "in": "formData", 839 | "required": true 840 | }, 841 | { 842 | "type": "file", 843 | "description": "Video File", 844 | "name": "video", 845 | "in": "formData", 846 | "required": true 847 | }, 848 | { 849 | "type": "boolean", 850 | "default": false, 851 | "description": "Is View Once", 852 | "name": "viewonce", 853 | "in": "formData" 854 | } 855 | ], 856 | "responses": { 857 | "200": { 858 | "description": "" 859 | } 860 | } 861 | } 862 | } 863 | }, 864 | "securityDefinitions": { 865 | "BasicAuth": { 866 | "type": "basic" 867 | }, 868 | "BearerAuth": { 869 | "type": "apiKey", 870 | "name": "Authorization", 871 | "in": "header" 872 | } 873 | } 874 | } -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | contact: 3 | email: drh.dimasrestu@gmail.com 4 | name: Dimas Restu Hidayanto 5 | url: https://github.com/dimaskiddo 6 | description: This is WhatsApp Multi-Device Implementation in Go REST API 7 | title: Go WhatsApp Multi-Device REST API 8 | version: 1.x 9 | paths: 10 | /: 11 | get: 12 | description: Get The Server Status 13 | produces: 14 | - application/json 15 | responses: 16 | "200": 17 | description: "" 18 | summary: Show The Status of The Server 19 | tags: 20 | - Root 21 | /auth: 22 | get: 23 | description: Get Authentication Token 24 | produces: 25 | - application/json 26 | responses: 27 | "200": 28 | description: "" 29 | security: 30 | - BasicAuth: [] 31 | summary: Generate Authentication Token 32 | tags: 33 | - Root 34 | /group: 35 | get: 36 | description: Get Joined Groups Information from WhatsApp 37 | produces: 38 | - application/json 39 | responses: 40 | "200": 41 | description: "" 42 | security: 43 | - BearerAuth: [] 44 | summary: Get Joined Groups Information 45 | tags: 46 | - WhatsApp Group 47 | /group/join: 48 | post: 49 | description: Joining to Group From Invitation Link from WhatsApp 50 | parameters: 51 | - description: Group Invitation Link 52 | in: formData 53 | name: link 54 | required: true 55 | type: string 56 | produces: 57 | - application/json 58 | responses: 59 | "200": 60 | description: "" 61 | security: 62 | - BearerAuth: [] 63 | summary: Join Group From Invitation Link 64 | tags: 65 | - WhatsApp Group 66 | /group/leave: 67 | post: 68 | description: Leaving Group By Group ID from WhatsApp 69 | parameters: 70 | - description: Group ID 71 | in: formData 72 | name: groupid 73 | required: true 74 | type: string 75 | produces: 76 | - application/json 77 | responses: 78 | "200": 79 | description: "" 80 | security: 81 | - BearerAuth: [] 82 | summary: Leave Group By Group ID 83 | tags: 84 | - WhatsApp Group 85 | /login: 86 | post: 87 | consumes: 88 | - multipart/form-data 89 | description: Get QR Code for WhatsApp Multi-Device Login 90 | parameters: 91 | - default: html 92 | description: Change Output Format in HTML or JSON 93 | enum: 94 | - html 95 | - json 96 | in: formData 97 | name: output 98 | type: string 99 | produces: 100 | - application/json 101 | - text/html 102 | responses: 103 | "200": 104 | description: "" 105 | security: 106 | - BearerAuth: [] 107 | summary: Generate QR Code for WhatsApp Multi-Device Login 108 | tags: 109 | - WhatsApp Authentication 110 | /login/pair: 111 | post: 112 | consumes: 113 | - multipart/form-data 114 | description: Get Pairing Code for WhatsApp Multi-Device Login 115 | produces: 116 | - application/json 117 | responses: 118 | "200": 119 | description: "" 120 | security: 121 | - BearerAuth: [] 122 | summary: Pair Phone for WhatsApp Multi-Device Login 123 | tags: 124 | - WhatsApp Authentication 125 | /logout: 126 | post: 127 | description: Make Device Logout from WhatsApp Multi-Device 128 | produces: 129 | - application/json 130 | responses: 131 | "200": 132 | description: "" 133 | security: 134 | - BearerAuth: [] 135 | summary: Logout Device from WhatsApp Multi-Device 136 | tags: 137 | - WhatsApp Authentication 138 | /message/delete: 139 | post: 140 | consumes: 141 | - multipart/form-data 142 | description: Delete Message to Spesific WhatsApp Personal ID or Group ID 143 | parameters: 144 | - description: Destination WhatsApp Personal ID or Group ID 145 | in: formData 146 | name: msisdn 147 | required: true 148 | type: string 149 | - description: Message ID 150 | in: formData 151 | name: messageid 152 | required: true 153 | type: string 154 | produces: 155 | - application/json 156 | responses: 157 | "200": 158 | description: "" 159 | security: 160 | - BearerAuth: [] 161 | summary: Delete Message 162 | tags: 163 | - WhatsApp Message 164 | /message/edit: 165 | post: 166 | consumes: 167 | - multipart/form-data 168 | description: Update Message to Spesific WhatsApp Personal ID or Group ID 169 | parameters: 170 | - description: Destination WhatsApp Personal ID or Group ID 171 | in: formData 172 | name: msisdn 173 | required: true 174 | type: string 175 | - description: Message ID 176 | in: formData 177 | name: messageid 178 | required: true 179 | type: string 180 | - description: Text Message 181 | in: formData 182 | name: message 183 | required: true 184 | type: string 185 | produces: 186 | - application/json 187 | responses: 188 | "200": 189 | description: "" 190 | security: 191 | - BearerAuth: [] 192 | summary: Update Message 193 | tags: 194 | - WhatsApp Message 195 | /message/react: 196 | post: 197 | consumes: 198 | - multipart/form-data 199 | description: React Message to Spesific WhatsApp Personal ID or Group ID 200 | parameters: 201 | - description: Destination WhatsApp Personal ID or Group ID 202 | in: formData 203 | name: msisdn 204 | required: true 205 | type: string 206 | - description: Message ID 207 | in: formData 208 | name: messageid 209 | required: true 210 | type: string 211 | - description: Reaction Emoji 212 | in: formData 213 | name: emoji 214 | required: true 215 | type: string 216 | produces: 217 | - application/json 218 | responses: 219 | "200": 220 | description: "" 221 | security: 222 | - BearerAuth: [] 223 | summary: React Message 224 | tags: 225 | - WhatsApp Message 226 | /registered: 227 | get: 228 | description: Check WhatsApp Personal ID is Registered 229 | parameters: 230 | - description: WhatsApp Personal ID to Check 231 | in: query 232 | name: msisdn 233 | required: true 234 | type: string 235 | produces: 236 | - application/json 237 | responses: 238 | "200": 239 | description: "" 240 | security: 241 | - BearerAuth: [] 242 | summary: Check If WhatsApp Personal ID is Registered 243 | tags: 244 | - WhatsApp Information 245 | /send/audio: 246 | post: 247 | consumes: 248 | - multipart/form-data 249 | description: Send Audio Message to Spesific WhatsApp Personal ID or Group ID 250 | parameters: 251 | - description: Destination WhatsApp Personal ID or Group ID 252 | in: formData 253 | name: msisdn 254 | required: true 255 | type: string 256 | - description: Audio File 257 | in: formData 258 | name: audio 259 | required: true 260 | type: file 261 | produces: 262 | - application/json 263 | responses: 264 | "200": 265 | description: "" 266 | security: 267 | - BearerAuth: [] 268 | summary: Send Audio Message 269 | tags: 270 | - WhatsApp Send Message 271 | /send/contact: 272 | post: 273 | consumes: 274 | - multipart/form-data 275 | description: Send Contact Message to Spesific WhatsApp Personal ID or Group 276 | ID 277 | parameters: 278 | - description: Destination WhatsApp Personal ID or Group ID 279 | in: formData 280 | name: msisdn 281 | required: true 282 | type: string 283 | - description: Contact Name 284 | in: formData 285 | name: name 286 | required: true 287 | type: string 288 | - description: Contact Phone 289 | in: formData 290 | name: phone 291 | required: true 292 | type: string 293 | produces: 294 | - application/json 295 | responses: 296 | "200": 297 | description: "" 298 | security: 299 | - BearerAuth: [] 300 | summary: Send Contact Message 301 | tags: 302 | - WhatsApp Send Message 303 | /send/document: 304 | post: 305 | consumes: 306 | - multipart/form-data 307 | description: Send Document Message to Spesific WhatsApp Personal ID or Group 308 | ID 309 | parameters: 310 | - description: Destination WhatsApp Personal ID or Group ID 311 | in: formData 312 | name: msisdn 313 | required: true 314 | type: string 315 | - description: Document File 316 | in: formData 317 | name: document 318 | required: true 319 | type: file 320 | produces: 321 | - application/json 322 | responses: 323 | "200": 324 | description: "" 325 | security: 326 | - BearerAuth: [] 327 | summary: Send Document Message 328 | tags: 329 | - WhatsApp Send Message 330 | /send/image: 331 | post: 332 | consumes: 333 | - multipart/form-data 334 | description: Send Image Message to Spesific WhatsApp Personal ID or Group ID 335 | parameters: 336 | - description: Destination WhatsApp Personal ID or Group ID 337 | in: formData 338 | name: msisdn 339 | required: true 340 | type: string 341 | - description: Caption Image Message 342 | in: formData 343 | name: caption 344 | required: true 345 | type: string 346 | - description: Image File 347 | in: formData 348 | name: image 349 | required: true 350 | type: file 351 | - default: false 352 | description: Is View Once 353 | in: formData 354 | name: viewonce 355 | type: boolean 356 | produces: 357 | - application/json 358 | responses: 359 | "200": 360 | description: "" 361 | security: 362 | - BearerAuth: [] 363 | summary: Send Image Message 364 | tags: 365 | - WhatsApp Send Message 366 | /send/link: 367 | post: 368 | consumes: 369 | - multipart/form-data 370 | description: Send Link Message to Spesific WhatsApp Personal ID or Group ID 371 | parameters: 372 | - description: Destination WhatsApp Personal ID or Group ID 373 | in: formData 374 | name: msisdn 375 | required: true 376 | type: string 377 | - description: Link Caption 378 | in: formData 379 | name: caption 380 | type: string 381 | - description: Link URL 382 | in: formData 383 | name: url 384 | required: true 385 | type: string 386 | produces: 387 | - application/json 388 | responses: 389 | "200": 390 | description: "" 391 | security: 392 | - BearerAuth: [] 393 | summary: Send Link Message 394 | tags: 395 | - WhatsApp Send Message 396 | /send/location: 397 | post: 398 | consumes: 399 | - multipart/form-data 400 | description: Send Location Message to Spesific WhatsApp Personal ID or Group 401 | ID 402 | parameters: 403 | - description: Destination WhatsApp Personal ID or Group ID 404 | in: formData 405 | name: msisdn 406 | required: true 407 | type: string 408 | - description: Location Latitude 409 | in: formData 410 | name: latitude 411 | required: true 412 | type: number 413 | - description: Location Longitude 414 | in: formData 415 | name: longitude 416 | required: true 417 | type: number 418 | produces: 419 | - application/json 420 | responses: 421 | "200": 422 | description: "" 423 | security: 424 | - BearerAuth: [] 425 | summary: Send Location Message 426 | tags: 427 | - WhatsApp Send Message 428 | /send/poll: 429 | post: 430 | consumes: 431 | - multipart/form-data 432 | description: Send Poll to Spesific WhatsApp Personal ID or Group ID 433 | parameters: 434 | - description: Destination WhatsApp Personal ID or Group ID 435 | in: formData 436 | name: msisdn 437 | required: true 438 | type: string 439 | - description: Poll Question 440 | in: formData 441 | name: question 442 | required: true 443 | type: string 444 | - description: Poll Options (Comma Seperated for New Options) 445 | in: formData 446 | name: options 447 | required: true 448 | type: string 449 | - default: false 450 | description: Is Multiple Answer 451 | in: formData 452 | name: multianswer 453 | type: boolean 454 | produces: 455 | - application/json 456 | responses: 457 | "200": 458 | description: "" 459 | security: 460 | - BearerAuth: [] 461 | summary: Send Poll 462 | tags: 463 | - WhatsApp Send Message 464 | /send/sticker: 465 | post: 466 | consumes: 467 | - multipart/form-data 468 | description: Send Sticker Message to Spesific WhatsApp Personal ID or Group 469 | ID 470 | parameters: 471 | - description: Destination WhatsApp Personal ID or Group ID 472 | in: formData 473 | name: msisdn 474 | required: true 475 | type: string 476 | - description: Sticker File 477 | in: formData 478 | name: sticker 479 | required: true 480 | type: file 481 | produces: 482 | - application/json 483 | responses: 484 | "200": 485 | description: "" 486 | security: 487 | - BearerAuth: [] 488 | summary: Send Sticker Message 489 | tags: 490 | - WhatsApp Send Message 491 | /send/text: 492 | post: 493 | consumes: 494 | - multipart/form-data 495 | description: Send Text Message to Spesific WhatsApp Personal ID or Group ID 496 | parameters: 497 | - description: Destination WhatsApp Personal ID or Group ID 498 | in: formData 499 | name: msisdn 500 | required: true 501 | type: string 502 | - description: Text Message 503 | in: formData 504 | name: message 505 | required: true 506 | type: string 507 | produces: 508 | - application/json 509 | responses: 510 | "200": 511 | description: "" 512 | security: 513 | - BearerAuth: [] 514 | summary: Send Text Message 515 | tags: 516 | - WhatsApp Send Message 517 | /send/video: 518 | post: 519 | consumes: 520 | - multipart/form-data 521 | description: Send Video Message to Spesific WhatsApp Personal ID or Group ID 522 | parameters: 523 | - description: Destination WhatsApp Personal ID or Group ID 524 | in: formData 525 | name: msisdn 526 | required: true 527 | type: string 528 | - description: Caption Video Message 529 | in: formData 530 | name: caption 531 | required: true 532 | type: string 533 | - description: Video File 534 | in: formData 535 | name: video 536 | required: true 537 | type: file 538 | - default: false 539 | description: Is View Once 540 | in: formData 541 | name: viewonce 542 | type: boolean 543 | produces: 544 | - application/json 545 | responses: 546 | "200": 547 | description: "" 548 | security: 549 | - BearerAuth: [] 550 | summary: Send Video Message 551 | tags: 552 | - WhatsApp Send Message 553 | securityDefinitions: 554 | BasicAuth: 555 | type: basic 556 | BearerAuth: 557 | in: header 558 | name: Authorization 559 | type: apiKey 560 | swagger: "2.0" 561 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dimaskiddo/go-whatsapp-multidevice-rest 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.6 6 | 7 | require ( 8 | github.com/PuerkitoBio/goquery v1.9.2 9 | github.com/SporkHubr/echo-http-cache v0.0.0-20200706100054-1d7ae9f38029 10 | github.com/forPelevin/gomoji v1.2.0 11 | github.com/go-playground/validator/v10 v10.23.0 12 | github.com/golang-jwt/jwt v3.2.2+incompatible 13 | github.com/joho/godotenv v1.5.1 14 | github.com/labstack/echo/v4 v4.12.0 15 | github.com/lib/pq v1.10.9 16 | github.com/nickalie/go-webpbin v0.0.0-20220110095747-f10016bf2dc1 17 | github.com/rivo/uniseg v0.4.7 18 | github.com/robfig/cron/v3 v3.0.1 19 | github.com/sirupsen/logrus v1.9.3 20 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e 21 | github.com/sunshineplan/imgconv v1.1.12 22 | github.com/swaggo/echo-swagger v1.4.1 23 | github.com/swaggo/swag v1.16.4 24 | go.mau.fi/whatsmeow v0.0.0-20250104105216-918c879fcd19 25 | google.golang.org/protobuf v1.36.1 26 | modernc.org/sqlite v1.17.0 27 | ) 28 | 29 | require ( 30 | filippo.io/edwards25519 v1.1.0 // indirect 31 | github.com/KyleBanks/depth v1.2.1 // indirect 32 | github.com/PuerkitoBio/purell v1.1.1 // indirect 33 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 34 | github.com/andybalholm/cascadia v1.3.2 // indirect 35 | github.com/dsnet/compress v0.0.1 // indirect 36 | github.com/frankban/quicktest v1.14.6 // indirect 37 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 38 | github.com/ghodss/yaml v1.0.0 // indirect 39 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 40 | github.com/go-openapi/jsonreference v0.19.6 // indirect 41 | github.com/go-openapi/spec v0.20.4 // indirect 42 | github.com/go-openapi/swag v0.19.15 // indirect 43 | github.com/go-playground/locales v0.14.1 // indirect 44 | github.com/go-playground/universal-translator v0.18.1 // indirect 45 | github.com/golang/snappy v0.0.4 // indirect 46 | github.com/google/uuid v1.6.0 // indirect 47 | github.com/gorilla/websocket v1.5.0 // indirect 48 | github.com/hhrutter/lzw v1.0.0 // indirect 49 | github.com/hhrutter/tiff v1.0.1 // indirect 50 | github.com/josharian/intern v1.0.0 // indirect 51 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 52 | github.com/labstack/gommon v0.4.2 // indirect 53 | github.com/leodido/go-urn v1.4.0 // indirect 54 | github.com/mailru/easyjson v0.7.7 // indirect 55 | github.com/mattn/go-colorable v0.1.13 // indirect 56 | github.com/mattn/go-isatty v0.0.20 // indirect 57 | github.com/mattn/go-runewidth v0.0.15 // indirect 58 | github.com/mholt/archiver v3.1.1+incompatible // indirect 59 | github.com/nickalie/go-binwrapper v0.0.0-20190114141239-525121d43c84 // indirect 60 | github.com/nwaples/rardecode v1.1.0 // indirect 61 | github.com/pdfcpu/pdfcpu v0.5.0 // indirect 62 | github.com/pierrec/lz4 v2.6.1+incompatible // indirect 63 | github.com/pkg/errors v0.9.1 // indirect 64 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect 65 | github.com/rs/zerolog v1.33.0 // indirect 66 | github.com/sunshineplan/pdf v1.0.7 // indirect 67 | github.com/swaggo/files/v2 v2.0.0 // indirect 68 | github.com/ulikunitz/xz v0.5.10 // indirect 69 | github.com/valyala/bytebufferpool v1.0.0 // indirect 70 | github.com/valyala/fasttemplate v1.2.2 // indirect 71 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 72 | go.mau.fi/libsignal v0.1.1 // indirect 73 | go.mau.fi/util v0.8.3 // indirect 74 | golang.org/x/crypto v0.31.0 // indirect 75 | golang.org/x/image v0.20.0 // indirect 76 | golang.org/x/mod v0.17.0 // indirect 77 | golang.org/x/net v0.33.0 // indirect 78 | golang.org/x/sync v0.10.0 // indirect 79 | golang.org/x/sys v0.28.0 // indirect 80 | golang.org/x/text v0.21.0 // indirect 81 | golang.org/x/time v0.5.0 // indirect 82 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 83 | gopkg.in/yaml.v2 v2.4.0 // indirect 84 | lukechampine.com/uint128 v1.1.1 // indirect 85 | modernc.org/cc/v3 v3.35.26 // indirect 86 | modernc.org/ccgo/v3 v3.16.2 // indirect 87 | modernc.org/libc v1.15.0 // indirect 88 | modernc.org/mathutil v1.4.1 // indirect 89 | modernc.org/memory v1.0.7 // indirect 90 | modernc.org/opt v0.1.1 // indirect 91 | modernc.org/strutil v1.1.1 // indirect 92 | modernc.org/token v1.0.0 // indirect 93 | ) 94 | -------------------------------------------------------------------------------- /internal/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/golang-jwt/jwt" 8 | "github.com/labstack/echo/v4" 9 | 10 | typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types" 11 | 12 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth" 13 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" 14 | ) 15 | 16 | // Auth 17 | // @Summary Generate Authentication Token 18 | // @Description Get Authentication Token 19 | // @Tags Root 20 | // @Produce json 21 | // @Success 200 22 | // @Security BasicAuth 23 | // @Router /auth [get] 24 | func Auth(c echo.Context) error { 25 | var reqAuthBasicInfo typAuth.RequestAuthBasicInfo 26 | var resAuthJWTData typAuth.ResponseAuthJWTData 27 | 28 | // Parse Basic Auth Information from Rewrited Body Request 29 | // By Basic Auth Middleware 30 | _ = json.NewDecoder(c.Request().Body).Decode(&reqAuthBasicInfo) 31 | 32 | // Create JWT Claims 33 | var jwtClaims *typAuth.AuthJWTClaims 34 | if auth.AuthJWTExpiredHour > 0 { 35 | jwtClaims = &typAuth.AuthJWTClaims{ 36 | typAuth.AuthJWTClaimsPayload{ 37 | JID: reqAuthBasicInfo.Username, 38 | }, 39 | jwt.StandardClaims{ 40 | IssuedAt: time.Now().Unix(), 41 | ExpiresAt: time.Now().Add(time.Hour * time.Duration(auth.AuthJWTExpiredHour)).Unix(), 42 | }, 43 | } 44 | } else { 45 | jwtClaims = &typAuth.AuthJWTClaims{ 46 | typAuth.AuthJWTClaimsPayload{ 47 | JID: reqAuthBasicInfo.Username, 48 | }, 49 | jwt.StandardClaims{ 50 | IssuedAt: time.Now().Unix(), 51 | }, 52 | } 53 | } 54 | 55 | // Create JWT Token 56 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims) 57 | 58 | // Generate Encoded JWT Token 59 | jwtTokenEncoded, err := jwtToken.SignedString([]byte(auth.AuthJWTSecret)) 60 | if err != nil { 61 | return router.ResponseInternalError(c, "") 62 | } 63 | 64 | // Set Encoded JWT Token as Response Data 65 | resAuthJWTData.Token = jwtTokenEncoded 66 | 67 | // Return JWT Token in JSON Response 68 | return router.ResponseSuccessWithData(c, "Successfully Authenticated", resAuthJWTData) 69 | } 70 | -------------------------------------------------------------------------------- /internal/auth/types/data.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/golang-jwt/jwt" 5 | ) 6 | 7 | type AuthJWTClaims struct { 8 | Data AuthJWTClaimsPayload `json:"dat"` 9 | jwt.StandardClaims 10 | } 11 | 12 | type AuthJWTClaimsPayload struct { 13 | JID string `json:"jid"` 14 | } 15 | -------------------------------------------------------------------------------- /internal/auth/types/request.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type RequestAuthBasicInfo struct { 4 | Username string 5 | } 6 | -------------------------------------------------------------------------------- /internal/auth/types/response.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type ResponseAuthJWTData struct { 4 | Token string `json:"token"` 5 | } 6 | -------------------------------------------------------------------------------- /internal/index/index.go: -------------------------------------------------------------------------------- 1 | package index 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | 6 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" 7 | ) 8 | 9 | // Index 10 | // @Summary Show The Status of The Server 11 | // @Description Get The Server Status 12 | // @Tags Root 13 | // @Produce json 14 | // @Success 200 15 | // @Router / [get] 16 | func Index(c echo.Context) error { 17 | return router.ResponseSuccess(c, "Go WhatsApp Multi-Device REST is running") 18 | } 19 | -------------------------------------------------------------------------------- /internal/route.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/labstack/echo/v4/middleware" 6 | 7 | eSwagger "github.com/swaggo/echo-swagger" 8 | 9 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/docs" 10 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth" 11 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" 12 | 13 | ctlAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth" 14 | typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types" 15 | 16 | ctlIndex "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index" 17 | ctlWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp" 18 | ) 19 | 20 | func Routes(e *echo.Echo) { 21 | // Configure OpenAPI / Swagger 22 | docs.SwaggerInfo.BasePath = router.BaseURL 23 | 24 | // Route for Index 25 | // --------------------------------------------- 26 | e.GET(router.BaseURL, ctlIndex.Index) 27 | e.GET(router.BaseURL+"/", ctlIndex.Index) 28 | 29 | // Route for OpenAPI / Swagger 30 | // --------------------------------------------- 31 | e.GET(router.BaseURL+"/docs/*", eSwagger.WrapHandler) 32 | 33 | // Route for Auth 34 | // --------------------------------------------- 35 | e.GET(router.BaseURL+"/auth", ctlAuth.Auth, auth.BasicAuth()) 36 | 37 | // Route for WhatsApp 38 | // --------------------------------------------- 39 | authJWTConfig := middleware.JWTConfig{ 40 | Claims: &typAuth.AuthJWTClaims{}, 41 | SigningKey: []byte(auth.AuthJWTSecret), 42 | } 43 | 44 | e.POST(router.BaseURL+"/login", ctlWhatsApp.Login, middleware.JWTWithConfig(authJWTConfig)) 45 | e.POST(router.BaseURL+"/login/pair", ctlWhatsApp.LoginPair, middleware.JWTWithConfig(authJWTConfig)) 46 | e.POST(router.BaseURL+"/logout", ctlWhatsApp.Logout, middleware.JWTWithConfig(authJWTConfig)) 47 | 48 | e.GET(router.BaseURL+"/registered", ctlWhatsApp.Registered, middleware.JWTWithConfig(authJWTConfig)) 49 | 50 | e.GET(router.BaseURL+"/group", ctlWhatsApp.GetGroup, middleware.JWTWithConfig(authJWTConfig)) 51 | e.POST(router.BaseURL+"/group/join", ctlWhatsApp.JoinGroup, middleware.JWTWithConfig(authJWTConfig)) 52 | e.POST(router.BaseURL+"/group/leave", ctlWhatsApp.LeaveGroup, middleware.JWTWithConfig(authJWTConfig)) 53 | 54 | e.POST(router.BaseURL+"/send/text", ctlWhatsApp.SendText, middleware.JWTWithConfig(authJWTConfig)) 55 | e.POST(router.BaseURL+"/send/location", ctlWhatsApp.SendLocation, middleware.JWTWithConfig(authJWTConfig)) 56 | e.POST(router.BaseURL+"/send/contact", ctlWhatsApp.SendContact, middleware.JWTWithConfig(authJWTConfig)) 57 | e.POST(router.BaseURL+"/send/link", ctlWhatsApp.SendLink, middleware.JWTWithConfig(authJWTConfig)) 58 | e.POST(router.BaseURL+"/send/document", ctlWhatsApp.SendDocument, middleware.JWTWithConfig(authJWTConfig)) 59 | e.POST(router.BaseURL+"/send/image", ctlWhatsApp.SendImage, middleware.JWTWithConfig(authJWTConfig)) 60 | e.POST(router.BaseURL+"/send/audio", ctlWhatsApp.SendAudio, middleware.JWTWithConfig(authJWTConfig)) 61 | e.POST(router.BaseURL+"/send/video", ctlWhatsApp.SendVideo, middleware.JWTWithConfig(authJWTConfig)) 62 | e.POST(router.BaseURL+"/send/sticker", ctlWhatsApp.SendSticker, middleware.JWTWithConfig(authJWTConfig)) 63 | e.POST(router.BaseURL+"/send/poll", ctlWhatsApp.SendPoll, middleware.JWTWithConfig(authJWTConfig)) 64 | 65 | e.POST(router.BaseURL+"/message/edit", ctlWhatsApp.MessageEdit, middleware.JWTWithConfig(authJWTConfig)) 66 | e.POST(router.BaseURL+"/message/react", ctlWhatsApp.MessageEdit, middleware.JWTWithConfig(authJWTConfig)) 67 | e.POST(router.BaseURL+"/message/delete", ctlWhatsApp.MessageDelete, middleware.JWTWithConfig(authJWTConfig)) 68 | } 69 | -------------------------------------------------------------------------------- /internal/routines.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/robfig/cron/v3" 5 | 6 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/log" 7 | pkgWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/whatsapp" 8 | ) 9 | 10 | func Routines(cron *cron.Cron) { 11 | log.Print(nil).Info("Running Routine Tasks") 12 | 13 | cron.AddFunc("0 * * * * *", func() { 14 | // If WhatsAppClient Connection is more than 0 15 | if len(pkgWhatsApp.WhatsAppClient) > 0 { 16 | // Check Every Authenticated MSISDN 17 | for jid, client := range pkgWhatsApp.WhatsAppClient { 18 | // Get Real JID from Datastore 19 | realJID := client.Store.ID.User 20 | 21 | // Mask JID for Logging Information 22 | maskJID := realJID[0:len(realJID)-4] + "xxxx" 23 | 24 | // Print Log Show Information of Device Checking 25 | log.Print(nil).Info("Checking WhatsApp Client for " + maskJID) 26 | 27 | // Check WhatsAppClient Registered JID with Authenticated MSISDN 28 | if jid != realJID { 29 | // Print Log Show Information to Force Log-out Device 30 | log.Print(nil).Info("Logging out WhatsApp Client for " + maskJID + " Due to Missmatch Authentication") 31 | 32 | // Logout WhatsAppClient Device 33 | _ = pkgWhatsApp.WhatsAppLogout(jid) 34 | delete(pkgWhatsApp.WhatsAppClient, jid) 35 | } 36 | } 37 | } 38 | }) 39 | 40 | cron.Start() 41 | } 42 | -------------------------------------------------------------------------------- /internal/startup.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/log" 5 | pkgWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/whatsapp" 6 | ) 7 | 8 | func Startup() { 9 | log.Print(nil).Info("Running Startup Tasks") 10 | 11 | // Load All WhatsApp Client Devices from Datastore 12 | devices, err := pkgWhatsApp.WhatsAppDatastore.GetAllDevices() 13 | if err != nil { 14 | log.Print(nil).Error("Failed to Load WhatsApp Client Devices from Datastore") 15 | } 16 | 17 | // Do Reconnect for Every Device in Datastore 18 | for _, device := range devices { 19 | // Get JID from Datastore 20 | jid := pkgWhatsApp.WhatsAppDecomposeJID(device.ID.User) 21 | 22 | // Mask JID for Logging Information 23 | maskJID := jid[0:len(jid)-4] + "xxxx" 24 | 25 | // Print Restore Log 26 | log.Print(nil).Info("Restoring WhatsApp Client for " + maskJID) 27 | 28 | // Initialize WhatsApp Client 29 | pkgWhatsApp.WhatsAppInitClient(device, jid) 30 | 31 | // Reconnect WhatsApp Client WebSocket 32 | err = pkgWhatsApp.WhatsAppReconnect(jid) 33 | if err != nil { 34 | log.Print(nil).Error(err.Error()) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/whatsapp/types/request.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type RequestLogin struct { 4 | Output string 5 | } 6 | 7 | type RequestSendMessage struct { 8 | RJID string 9 | Message string 10 | ViewOnce bool 11 | } 12 | 13 | type RequestSendLocation struct { 14 | RJID string 15 | Latitude float64 16 | Longitude float64 17 | } 18 | 19 | type RequestSendContact struct { 20 | RJID string 21 | Name string 22 | Phone string 23 | } 24 | 25 | type RequestSendLink struct { 26 | RJID string 27 | Caption string 28 | URL string 29 | } 30 | 31 | type RequestSendPoll struct { 32 | RJID string 33 | Question string 34 | Options string 35 | MultiAnswer bool 36 | } 37 | 38 | type RequestMessage struct { 39 | RJID string 40 | MSGID string 41 | Message string 42 | Emoji string 43 | } 44 | 45 | type RequestGroupJoin struct { 46 | Link string 47 | } 48 | 49 | type RequestGroupLeave struct { 50 | GID string 51 | } 52 | -------------------------------------------------------------------------------- /internal/whatsapp/types/response.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type ResponseLogin struct { 4 | QRCode string `json:"qrcode"` 5 | Timeout int `json:"timeout"` 6 | } 7 | 8 | type ResponsePairing struct { 9 | PairCode string `json:"paircode"` 10 | Timeout int `json:"timeout"` 11 | } 12 | 13 | type ResponseSendMessage struct { 14 | MsgID string `json:"msgid"` 15 | } 16 | -------------------------------------------------------------------------------- /internal/whatsapp/whatsapp.go: -------------------------------------------------------------------------------- 1 | package whatsapp 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "mime/multipart" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/golang-jwt/jwt" 11 | "github.com/labstack/echo/v4" 12 | 13 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" 14 | pkgWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/whatsapp" 15 | 16 | typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types" 17 | typWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp/types" 18 | ) 19 | 20 | func jwtPayload(c echo.Context) typAuth.AuthJWTClaimsPayload { 21 | jwtToken := c.Get("user").(*jwt.Token) 22 | jwtClaims := jwtToken.Claims.(*typAuth.AuthJWTClaims) 23 | 24 | return jwtClaims.Data 25 | } 26 | 27 | func convertFileToBytes(file multipart.File) ([]byte, error) { 28 | // Create Empty Buffer 29 | buffer := bytes.NewBuffer(nil) 30 | 31 | // Copy File Stream to Buffer 32 | _, err := io.Copy(buffer, file) 33 | if err != nil { 34 | return bytes.NewBuffer(nil).Bytes(), err 35 | } 36 | 37 | return buffer.Bytes(), nil 38 | } 39 | 40 | // Login 41 | // @Summary Generate QR Code for WhatsApp Multi-Device Login 42 | // @Description Get QR Code for WhatsApp Multi-Device Login 43 | // @Tags WhatsApp Authentication 44 | // @Accept multipart/form-data 45 | // @Produce json 46 | // @Produce html 47 | // @Param output formData string false "Change Output Format in HTML or JSON" Enums(html, json) default(html) 48 | // @Success 200 49 | // @Security BearerAuth 50 | // @Router /login [post] 51 | func Login(c echo.Context) error { 52 | var err error 53 | jid := jwtPayload(c).JID 54 | 55 | var reqLogin typWhatsApp.RequestLogin 56 | reqLogin.Output = strings.TrimSpace(c.FormValue("output")) 57 | 58 | if len(reqLogin.Output) == 0 { 59 | reqLogin.Output = "html" 60 | } 61 | 62 | // Initialize WhatsApp Client 63 | pkgWhatsApp.WhatsAppInitClient(nil, jid) 64 | 65 | // Get WhatsApp QR Code Image 66 | qrCodeImage, qrCodeTimeout, err := pkgWhatsApp.WhatsAppLogin(jid) 67 | if err != nil { 68 | return router.ResponseInternalError(c, err.Error()) 69 | } 70 | 71 | // If Return is Not QR Code But Reconnected 72 | // Then Return OK With Reconnected Status 73 | if qrCodeImage == "WhatsApp Client is Reconnected" { 74 | return router.ResponseSuccess(c, qrCodeImage) 75 | } 76 | 77 | var resLogin typWhatsApp.ResponseLogin 78 | resLogin.QRCode = qrCodeImage 79 | resLogin.Timeout = qrCodeTimeout 80 | 81 | if reqLogin.Output == "html" { 82 | htmlContent := ` 83 | 84 | 85 | WhatsApp Multi-Device Login 86 | 87 | 88 | 89 | 90 |

91 | QR Code Scan 92 |
93 | Timeout in ` + strconv.Itoa(resLogin.Timeout) + ` Second(s) 94 |

95 | 96 | ` 97 | 98 | return router.ResponseSuccessWithHTML(c, htmlContent) 99 | } 100 | 101 | return router.ResponseSuccessWithData(c, "Successfully Generated QR Code", resLogin) 102 | } 103 | 104 | // PairPhone 105 | // @Summary Pair Phone for WhatsApp Multi-Device Login 106 | // @Description Get Pairing Code for WhatsApp Multi-Device Login 107 | // @Tags WhatsApp Authentication 108 | // @Accept multipart/form-data 109 | // @Produce json 110 | // @Success 200 111 | // @Security BearerAuth 112 | // @Router /login/pair [post] 113 | func LoginPair(c echo.Context) error { 114 | var err error 115 | jid := jwtPayload(c).JID 116 | 117 | // Initialize WhatsApp Client 118 | pkgWhatsApp.WhatsAppInitClient(nil, jid) 119 | 120 | // Get WhatsApp pairing Code text 121 | pairCode, pairCodeTimeout, err := pkgWhatsApp.WhatsAppLoginPair(jid) 122 | if err != nil { 123 | return router.ResponseInternalError(c, err.Error()) 124 | } 125 | 126 | // If Return is not pairing code but Reconnected 127 | // Then Return OK With Reconnected Status 128 | if pairCode == "WhatsApp Client is Reconnected" { 129 | return router.ResponseSuccess(c, pairCode) 130 | } 131 | 132 | var resPairing typWhatsApp.ResponsePairing 133 | resPairing.PairCode = pairCode 134 | resPairing.Timeout = pairCodeTimeout 135 | 136 | return router.ResponseSuccessWithData(c, "Successfully Generated Pairing Code", resPairing) 137 | } 138 | 139 | // Logout 140 | // @Summary Logout Device from WhatsApp Multi-Device 141 | // @Description Make Device Logout from WhatsApp Multi-Device 142 | // @Tags WhatsApp Authentication 143 | // @Produce json 144 | // @Success 200 145 | // @Security BearerAuth 146 | // @Router /logout [post] 147 | func Logout(c echo.Context) error { 148 | var err error 149 | jid := jwtPayload(c).JID 150 | 151 | err = pkgWhatsApp.WhatsAppLogout(jid) 152 | if err != nil { 153 | return router.ResponseInternalError(c, err.Error()) 154 | } 155 | 156 | return router.ResponseSuccess(c, "Successfully Logged Out") 157 | } 158 | 159 | // Registered 160 | // @Summary Check If WhatsApp Personal ID is Registered 161 | // @Description Check WhatsApp Personal ID is Registered 162 | // @Tags WhatsApp Information 163 | // @Produce json 164 | // @Param msisdn query string true "WhatsApp Personal ID to Check" 165 | // @Success 200 166 | // @Security BearerAuth 167 | // @Router /registered [get] 168 | func Registered(c echo.Context) error { 169 | jid := jwtPayload(c).JID 170 | remoteJID := strings.TrimSpace(c.QueryParam("msisdn")) 171 | 172 | if len(remoteJID) == 0 { 173 | return router.ResponseInternalError(c, "Missing Query Value MSISDN") 174 | } 175 | 176 | err := pkgWhatsApp.WhatsAppCheckRegistered(jid, remoteJID) 177 | if err != nil { 178 | return router.ResponseInternalError(c, err.Error()) 179 | } 180 | 181 | return router.ResponseSuccess(c, "WhatsApp Personal ID is Registered") 182 | } 183 | 184 | // GetGroup 185 | // @Summary Get Joined Groups Information 186 | // @Description Get Joined Groups Information from WhatsApp 187 | // @Tags WhatsApp Group 188 | // @Produce json 189 | // @Success 200 190 | // @Security BearerAuth 191 | // @Router /group [get] 192 | func GetGroup(c echo.Context) error { 193 | var err error 194 | jid := jwtPayload(c).JID 195 | 196 | group, err := pkgWhatsApp.WhatsAppGroupGet(jid) 197 | if err != nil { 198 | return router.ResponseInternalError(c, err.Error()) 199 | } 200 | 201 | return router.ResponseSuccessWithData(c, "Successfully List Joined Groups", group) 202 | } 203 | 204 | // JoinGroup 205 | // @Summary Join Group From Invitation Link 206 | // @Description Joining to Group From Invitation Link from WhatsApp 207 | // @Tags WhatsApp Group 208 | // @Produce json 209 | // @Param link formData string true "Group Invitation Link" 210 | // @Success 200 211 | // @Security BearerAuth 212 | // @Router /group/join [post] 213 | func JoinGroup(c echo.Context) error { 214 | var err error 215 | jid := jwtPayload(c).JID 216 | 217 | var reqGroupJoin typWhatsApp.RequestGroupJoin 218 | reqGroupJoin.Link = strings.TrimSpace(c.FormValue("link")) 219 | 220 | if len(reqGroupJoin.Link) == 0 { 221 | return router.ResponseBadRequest(c, "Missing Form Value Link") 222 | } 223 | 224 | group, err := pkgWhatsApp.WhatsAppGroupJoin(jid, reqGroupJoin.Link) 225 | if err != nil { 226 | return router.ResponseInternalError(c, err.Error()) 227 | } 228 | 229 | return router.ResponseSuccessWithData(c, "Successfully Joined Group From Invitation Link", group) 230 | } 231 | 232 | // LeaveGroup 233 | // @Summary Leave Group By Group ID 234 | // @Description Leaving Group By Group ID from WhatsApp 235 | // @Tags WhatsApp Group 236 | // @Produce json 237 | // @Param groupid formData string true "Group ID" 238 | // @Success 200 239 | // @Security BearerAuth 240 | // @Router /group/leave [post] 241 | func LeaveGroup(c echo.Context) error { 242 | var err error 243 | jid := jwtPayload(c).JID 244 | 245 | var reqGroupLeave typWhatsApp.RequestGroupLeave 246 | reqGroupLeave.GID = strings.TrimSpace(c.FormValue("groupid")) 247 | 248 | if len(reqGroupLeave.GID) == 0 { 249 | return router.ResponseBadRequest(c, "Missing Form Value Group ID") 250 | } 251 | 252 | err = pkgWhatsApp.WhatsAppGroupLeave(jid, reqGroupLeave.GID) 253 | if err != nil { 254 | return router.ResponseInternalError(c, err.Error()) 255 | } 256 | 257 | return router.ResponseSuccess(c, "Successfully Leave Group By Group ID") 258 | } 259 | 260 | // SendText 261 | // @Summary Send Text Message 262 | // @Description Send Text Message to Spesific WhatsApp Personal ID or Group ID 263 | // @Tags WhatsApp Send Message 264 | // @Accept multipart/form-data 265 | // @Produce json 266 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 267 | // @Param message formData string true "Text Message" 268 | // @Success 200 269 | // @Security BearerAuth 270 | // @Router /send/text [post] 271 | func SendText(c echo.Context) error { 272 | var err error 273 | jid := jwtPayload(c).JID 274 | 275 | var reqSendMessage typWhatsApp.RequestSendMessage 276 | reqSendMessage.RJID = strings.TrimSpace(c.FormValue("msisdn")) 277 | reqSendMessage.Message = strings.Replace(strings.TrimSpace(c.FormValue("message")), "\\n", "\n", -1) 278 | 279 | if len(reqSendMessage.RJID) == 0 { 280 | return router.ResponseBadRequest(c, "Missing Form Value MSISDN") 281 | } 282 | 283 | if len(reqSendMessage.Message) == 0 { 284 | return router.ResponseBadRequest(c, "Missing Form Value Message") 285 | } 286 | 287 | var resSendMessage typWhatsApp.ResponseSendMessage 288 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendText(c.Request().Context(), jid, reqSendMessage.RJID, reqSendMessage.Message) 289 | if err != nil { 290 | return router.ResponseInternalError(c, err.Error()) 291 | } 292 | 293 | return router.ResponseSuccessWithData(c, "Successfully Send Text Message", resSendMessage) 294 | } 295 | 296 | // SendLocation 297 | // @Summary Send Location Message 298 | // @Description Send Location Message to Spesific WhatsApp Personal ID or Group ID 299 | // @Tags WhatsApp Send Message 300 | // @Accept multipart/form-data 301 | // @Produce json 302 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 303 | // @Param latitude formData number true "Location Latitude" 304 | // @Param longitude formData number true "Location Longitude" 305 | // @Success 200 306 | // @Security BearerAuth 307 | // @Router /send/location [post] 308 | func SendLocation(c echo.Context) error { 309 | var err error 310 | jid := jwtPayload(c).JID 311 | 312 | var reqSendLocation typWhatsApp.RequestSendLocation 313 | reqSendLocation.RJID = strings.TrimSpace(c.FormValue("msisdn")) 314 | 315 | reqSendLocation.Latitude, err = strconv.ParseFloat(strings.TrimSpace(c.FormValue("latitude")), 64) 316 | if err != nil { 317 | return router.ResponseInternalError(c, "Error While Decoding Latitude to Float64") 318 | } 319 | 320 | reqSendLocation.Longitude, err = strconv.ParseFloat(strings.TrimSpace(c.FormValue("longitude")), 64) 321 | if err != nil { 322 | return router.ResponseInternalError(c, "Error While Decoding Longitude to Float64") 323 | } 324 | 325 | if len(reqSendLocation.RJID) == 0 { 326 | return router.ResponseBadRequest(c, "Missing Form Value MSISDN") 327 | } 328 | 329 | var resSendMessage typWhatsApp.ResponseSendMessage 330 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendLocation(c.Request().Context(), jid, reqSendLocation.RJID, reqSendLocation.Latitude, reqSendLocation.Longitude) 331 | if err != nil { 332 | return router.ResponseInternalError(c, err.Error()) 333 | } 334 | 335 | return router.ResponseSuccessWithData(c, "Successfully Send Location Message", resSendMessage) 336 | } 337 | 338 | // SendContact 339 | // @Summary Send Contact Message 340 | // @Description Send Contact Message to Spesific WhatsApp Personal ID or Group ID 341 | // @Tags WhatsApp Send Message 342 | // @Accept multipart/form-data 343 | // @Produce json 344 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 345 | // @Param name formData string true "Contact Name" 346 | // @Param phone formData string true "Contact Phone" 347 | // @Success 200 348 | // @Security BearerAuth 349 | // @Router /send/contact [post] 350 | func SendContact(c echo.Context) error { 351 | var err error 352 | jid := jwtPayload(c).JID 353 | 354 | var reqSendContact typWhatsApp.RequestSendContact 355 | reqSendContact.RJID = strings.TrimSpace(c.FormValue("msisdn")) 356 | reqSendContact.Name = strings.TrimSpace(c.FormValue("name")) 357 | reqSendContact.Phone = strings.TrimSpace(c.FormValue("phone")) 358 | 359 | if len(reqSendContact.RJID) == 0 { 360 | return router.ResponseBadRequest(c, "Missing Form Value MSISDN") 361 | } 362 | 363 | if len(reqSendContact.Name) == 0 { 364 | return router.ResponseBadRequest(c, "Missing Form Value Name") 365 | } 366 | 367 | if len(reqSendContact.Phone) == 0 { 368 | return router.ResponseBadRequest(c, "Missing Form Value Phone") 369 | } 370 | 371 | var resSendMessage typWhatsApp.ResponseSendMessage 372 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendContact(c.Request().Context(), jid, reqSendContact.RJID, reqSendContact.Name, reqSendContact.Phone) 373 | if err != nil { 374 | return router.ResponseInternalError(c, err.Error()) 375 | } 376 | 377 | return router.ResponseSuccessWithData(c, "Successfully Send Contact Message", resSendMessage) 378 | } 379 | 380 | // SendLink 381 | // @Summary Send Link Message 382 | // @Description Send Link Message to Spesific WhatsApp Personal ID or Group ID 383 | // @Tags WhatsApp Send Message 384 | // @Accept multipart/form-data 385 | // @Produce json 386 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 387 | // @Param caption formData string false "Link Caption" 388 | // @Param url formData string true "Link URL" 389 | // @Success 200 390 | // @Security BearerAuth 391 | // @Router /send/link [post] 392 | func SendLink(c echo.Context) error { 393 | var err error 394 | jid := jwtPayload(c).JID 395 | 396 | var reqSendLink typWhatsApp.RequestSendLink 397 | reqSendLink.RJID = strings.TrimSpace(c.FormValue("msisdn")) 398 | reqSendLink.Caption = strings.TrimSpace(c.FormValue("caption")) 399 | reqSendLink.URL = strings.TrimSpace(c.FormValue("url")) 400 | 401 | if len(reqSendLink.RJID) == 0 { 402 | return router.ResponseBadRequest(c, "Missing Form Value MSISDN") 403 | } 404 | 405 | if len(reqSendLink.URL) == 0 { 406 | return router.ResponseBadRequest(c, "Missing Form Value URL") 407 | } 408 | 409 | var resSendMessage typWhatsApp.ResponseSendMessage 410 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendLink(c.Request().Context(), jid, reqSendLink.RJID, reqSendLink.Caption, reqSendLink.URL) 411 | if err != nil { 412 | return router.ResponseInternalError(c, err.Error()) 413 | } 414 | 415 | return router.ResponseSuccessWithData(c, "Successfully Send Link Message", resSendMessage) 416 | } 417 | 418 | // SendDocument 419 | // @Summary Send Document Message 420 | // @Description Send Document Message to Spesific WhatsApp Personal ID or Group ID 421 | // @Tags WhatsApp Send Message 422 | // @Accept multipart/form-data 423 | // @Produce json 424 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 425 | // @Param document formData file true "Document File" 426 | // @Success 200 427 | // @Security BearerAuth 428 | // @Router /send/document [post] 429 | func SendDocument(c echo.Context) error { 430 | return sendMedia(c, "document") 431 | } 432 | 433 | // SendImage 434 | // @Summary Send Image Message 435 | // @Description Send Image Message to Spesific WhatsApp Personal ID or Group ID 436 | // @Tags WhatsApp Send Message 437 | // @Accept multipart/form-data 438 | // @Produce json 439 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 440 | // @Param caption formData string true "Caption Image Message" 441 | // @Param image formData file true "Image File" 442 | // @Param viewonce formData bool false "Is View Once" default(false) 443 | // @Success 200 444 | // @Security BearerAuth 445 | // @Router /send/image [post] 446 | func SendImage(c echo.Context) error { 447 | return sendMedia(c, "image") 448 | } 449 | 450 | // SendAudio 451 | // @Summary Send Audio Message 452 | // @Description Send Audio Message to Spesific WhatsApp Personal ID or Group ID 453 | // @Tags WhatsApp Send Message 454 | // @Accept multipart/form-data 455 | // @Produce json 456 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 457 | // @Param audio formData file true "Audio File" 458 | // @Success 200 459 | // @Security BearerAuth 460 | // @Router /send/audio [post] 461 | func SendAudio(c echo.Context) error { 462 | return sendMedia(c, "audio") 463 | } 464 | 465 | // SendVideo 466 | // @Summary Send Video Message 467 | // @Description Send Video Message to Spesific WhatsApp Personal ID or Group ID 468 | // @Tags WhatsApp Send Message 469 | // @Accept multipart/form-data 470 | // @Produce json 471 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 472 | // @Param caption formData string true "Caption Video Message" 473 | // @Param video formData file true "Video File" 474 | // @Param viewonce formData bool false "Is View Once" default(false) 475 | // @Success 200 476 | // @Security BearerAuth 477 | // @Router /send/video [post] 478 | func SendVideo(c echo.Context) error { 479 | return sendMedia(c, "video") 480 | } 481 | 482 | // SendSticker 483 | // @Summary Send Sticker Message 484 | // @Description Send Sticker Message to Spesific WhatsApp Personal ID or Group ID 485 | // @Tags WhatsApp Send Message 486 | // @Accept multipart/form-data 487 | // @Produce json 488 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 489 | // @Param sticker formData file true "Sticker File" 490 | // @Success 200 491 | // @Security BearerAuth 492 | // @Router /send/sticker [post] 493 | func SendSticker(c echo.Context) error { 494 | return sendMedia(c, "sticker") 495 | } 496 | 497 | func sendMedia(c echo.Context, mediaType string) error { 498 | var err error 499 | jid := jwtPayload(c).JID 500 | 501 | var reqSendMessage typWhatsApp.RequestSendMessage 502 | reqSendMessage.RJID = strings.TrimSpace(c.FormValue("msisdn")) 503 | 504 | // Read Uploaded File Based on Send Media Type 505 | var fileStream multipart.File 506 | var fileHeader *multipart.FileHeader 507 | 508 | switch mediaType { 509 | case "document": 510 | fileStream, fileHeader, err = c.Request().FormFile("document") 511 | reqSendMessage.Message = fileHeader.Filename 512 | 513 | case "image": 514 | fileStream, fileHeader, err = c.Request().FormFile("image") 515 | reqSendMessage.Message = strings.TrimSpace(c.FormValue("caption")) 516 | 517 | case "audio": 518 | fileStream, fileHeader, err = c.Request().FormFile("audio") 519 | 520 | case "video": 521 | fileStream, fileHeader, err = c.Request().FormFile("video") 522 | reqSendMessage.Message = strings.TrimSpace(c.FormValue("caption")) 523 | 524 | case "sticker": 525 | fileStream, fileHeader, err = c.Request().FormFile("sticker") 526 | } 527 | 528 | // Don't Forget to Close The File Stream 529 | defer fileStream.Close() 530 | 531 | // Get Uploaded File MIME Type 532 | fileType := fileHeader.Header.Get("Content-Type") 533 | 534 | // If There are Some Errors While Opeening The File Stream 535 | // Return Bad Request with Original Error Message 536 | if err != nil { 537 | return router.ResponseBadRequest(c, err.Error()) 538 | } 539 | 540 | // Make Sure RJID is Filled 541 | if len(reqSendMessage.RJID) == 0 { 542 | return router.ResponseBadRequest(c, "Missing Form Value MSISDN") 543 | } 544 | 545 | // Check if Media Type is "image" or "video" 546 | // Then Parse ViewOnce Parameter 547 | if mediaType == "image" || mediaType == "video" { 548 | isViewOnce := strings.TrimSpace(c.FormValue("viewonce")) 549 | 550 | if len(isViewOnce) == 0 { 551 | // If ViewOnce Parameter Doesn't Exist or Empty String 552 | // Then Set it Default to False 553 | reqSendMessage.ViewOnce = false 554 | } else { 555 | // If ViewOnce Parameter is not Empty 556 | // Then Parse it to Bool 557 | reqSendMessage.ViewOnce, err = strconv.ParseBool(isViewOnce) 558 | if err != nil { 559 | return router.ResponseBadRequest(c, err.Error()) 560 | } 561 | } 562 | } 563 | 564 | // Convert File Stream in to Bytes 565 | // Since WhatsApp Proto for Media is only Accepting Bytes format 566 | fileBytes, err := convertFileToBytes(fileStream) 567 | if err != nil { 568 | return router.ResponseInternalError(c, err.Error()) 569 | } 570 | 571 | // Send Media Message Based on Media Type 572 | ctx := c.Request().Context() 573 | var resSendMessage typWhatsApp.ResponseSendMessage 574 | switch mediaType { 575 | case "document": 576 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendDocument(ctx, jid, reqSendMessage.RJID, fileBytes, fileType, reqSendMessage.Message) 577 | 578 | case "image": 579 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendImage(ctx, jid, reqSendMessage.RJID, fileBytes, fileType, reqSendMessage.Message, reqSendMessage.ViewOnce) 580 | 581 | case "audio": 582 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendAudio(ctx, jid, reqSendMessage.RJID, fileBytes, fileType) 583 | 584 | case "video": 585 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendVideo(ctx, jid, reqSendMessage.RJID, fileBytes, fileType, reqSendMessage.Message, reqSendMessage.ViewOnce) 586 | 587 | case "sticker": 588 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendSticker(ctx, jid, reqSendMessage.RJID, fileBytes) 589 | } 590 | 591 | // Return Internal Server Error 592 | // When Detected There are Some Errors While Sending The Media Message 593 | if err != nil { 594 | return router.ResponseInternalError(c, err.Error()) 595 | } 596 | 597 | return router.ResponseSuccessWithData(c, "Successfully Send Media Message", resSendMessage) 598 | } 599 | 600 | // SendPoll 601 | // @Summary Send Poll 602 | // @Description Send Poll to Spesific WhatsApp Personal ID or Group ID 603 | // @Tags WhatsApp Send Message 604 | // @Accept multipart/form-data 605 | // @Produce json 606 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 607 | // @Param question formData string true "Poll Question" 608 | // @Param options formData string true "Poll Options (Comma Seperated for New Options)" 609 | // @Param multianswer formData bool false "Is Multiple Answer" default(false) 610 | // @Success 200 611 | // @Security BearerAuth 612 | // @Router /send/poll [post] 613 | func SendPoll(c echo.Context) error { 614 | var err error 615 | jid := jwtPayload(c).JID 616 | 617 | var reqSendPoll typWhatsApp.RequestSendPoll 618 | reqSendPoll.RJID = strings.TrimSpace(c.FormValue("msisdn")) 619 | reqSendPoll.Question = strings.TrimSpace(c.FormValue("question")) 620 | reqSendPoll.Options = strings.TrimSpace(c.FormValue("options")) 621 | 622 | if len(reqSendPoll.RJID) == 0 { 623 | return router.ResponseBadRequest(c, "Missing Form Value MSISDN") 624 | } 625 | 626 | if len(reqSendPoll.Question) == 0 { 627 | return router.ResponseBadRequest(c, "Missing Form Value Question") 628 | } 629 | 630 | if len(reqSendPoll.Options) == 0 { 631 | return router.ResponseBadRequest(c, "Missing Form Value Options") 632 | } 633 | 634 | isMultiAnswer := strings.TrimSpace(c.FormValue("multianswer")) 635 | if len(isMultiAnswer) == 0 { 636 | // If MultiAnswer Parameter Doesn't Exist or Empty String 637 | // Then Set it Default to False 638 | reqSendPoll.MultiAnswer = false 639 | } else { 640 | // If MultiAnswer Parameter is not Empty 641 | // Then Parse it to Bool 642 | reqSendPoll.MultiAnswer, err = strconv.ParseBool(isMultiAnswer) 643 | if err != nil { 644 | return router.ResponseBadRequest(c, err.Error()) 645 | } 646 | } 647 | 648 | pollOptions := strings.Split(reqSendPoll.Options, ",") 649 | for i, str := range pollOptions { 650 | pollOptions[i] = strings.TrimSpace(str) 651 | } 652 | 653 | var resSendMessage typWhatsApp.ResponseSendMessage 654 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendPoll(c.Request().Context(), jid, reqSendPoll.RJID, reqSendPoll.Question, pollOptions, reqSendPoll.MultiAnswer) 655 | if err != nil { 656 | return router.ResponseInternalError(c, err.Error()) 657 | } 658 | 659 | return router.ResponseSuccessWithData(c, "Successfully Send Poll Message", resSendMessage) 660 | } 661 | 662 | // MessageEdit 663 | // @Summary Update Message 664 | // @Description Update Message to Spesific WhatsApp Personal ID or Group ID 665 | // @Tags WhatsApp Message 666 | // @Accept multipart/form-data 667 | // @Produce json 668 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 669 | // @Param messageid formData string true "Message ID" 670 | // @Param message formData string true "Text Message" 671 | // @Success 200 672 | // @Security BearerAuth 673 | // @Router /message/edit [post] 674 | func MessageEdit(c echo.Context) error { 675 | var err error 676 | jid := jwtPayload(c).JID 677 | 678 | var reqMessageUpdate typWhatsApp.RequestMessage 679 | reqMessageUpdate.RJID = strings.TrimSpace(c.FormValue("msisdn")) 680 | reqMessageUpdate.MSGID = strings.TrimSpace(c.FormValue("messageid")) 681 | reqMessageUpdate.Message = strings.TrimSpace(c.FormValue("message")) 682 | 683 | if len(reqMessageUpdate.RJID) == 0 { 684 | return router.ResponseBadRequest(c, "Missing Form Value MSISDN") 685 | } 686 | 687 | if len(reqMessageUpdate.MSGID) == 0 { 688 | return router.ResponseBadRequest(c, "Missing Form Value Message ID") 689 | } 690 | 691 | if len(reqMessageUpdate.Message) == 0 { 692 | return router.ResponseBadRequest(c, "Missing Form Value Message") 693 | } 694 | 695 | var resSendMessage typWhatsApp.ResponseSendMessage 696 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppMessageEdit(c.Request().Context(), jid, reqMessageUpdate.RJID, reqMessageUpdate.MSGID, reqMessageUpdate.Message) 697 | if err != nil { 698 | return router.ResponseInternalError(c, err.Error()) 699 | } 700 | 701 | return router.ResponseSuccessWithData(c, "Successfully Update Message", resSendMessage) 702 | } 703 | 704 | // MessageReact 705 | // @Summary React Message 706 | // @Description React Message to Spesific WhatsApp Personal ID or Group ID 707 | // @Tags WhatsApp Message 708 | // @Accept multipart/form-data 709 | // @Produce json 710 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 711 | // @Param messageid formData string true "Message ID" 712 | // @Param emoji formData string true "Reaction Emoji" 713 | // @Success 200 714 | // @Security BearerAuth 715 | // @Router /message/react [post] 716 | func MessageReact(c echo.Context) error { 717 | var err error 718 | jid := jwtPayload(c).JID 719 | 720 | var reqMessageUpdate typWhatsApp.RequestMessage 721 | reqMessageUpdate.RJID = strings.TrimSpace(c.FormValue("msisdn")) 722 | reqMessageUpdate.MSGID = strings.TrimSpace(c.FormValue("messageid")) 723 | reqMessageUpdate.Emoji = strings.TrimSpace(c.FormValue("emoji")) 724 | 725 | if len(reqMessageUpdate.RJID) == 0 { 726 | return router.ResponseBadRequest(c, "Missing Form Value MSISDN") 727 | } 728 | 729 | if len(reqMessageUpdate.MSGID) == 0 { 730 | return router.ResponseBadRequest(c, "Missing Form Value Message ID") 731 | } 732 | 733 | if len(reqMessageUpdate.Emoji) == 0 { 734 | return router.ResponseBadRequest(c, "Missing Form Value Emoji") 735 | } 736 | 737 | var resSendMessage typWhatsApp.ResponseSendMessage 738 | resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppMessageReact(c.Request().Context(), jid, reqMessageUpdate.RJID, reqMessageUpdate.MSGID, reqMessageUpdate.Emoji) 739 | if err != nil { 740 | return router.ResponseInternalError(c, err.Error()) 741 | } 742 | 743 | return router.ResponseSuccessWithData(c, "Successfully React Message", resSendMessage) 744 | } 745 | 746 | // MessageDelete 747 | // @Summary Delete Message 748 | // @Description Delete Message to Spesific WhatsApp Personal ID or Group ID 749 | // @Tags WhatsApp Message 750 | // @Accept multipart/form-data 751 | // @Produce json 752 | // @Param msisdn formData string true "Destination WhatsApp Personal ID or Group ID" 753 | // @Param messageid formData string true "Message ID" 754 | // @Success 200 755 | // @Security BearerAuth 756 | // @Router /message/delete [post] 757 | func MessageDelete(c echo.Context) error { 758 | var err error 759 | jid := jwtPayload(c).JID 760 | 761 | var reqMessageUpdate typWhatsApp.RequestMessage 762 | reqMessageUpdate.RJID = strings.TrimSpace(c.FormValue("msisdn")) 763 | reqMessageUpdate.MSGID = strings.TrimSpace(c.FormValue("messageid")) 764 | 765 | if len(reqMessageUpdate.RJID) == 0 { 766 | return router.ResponseBadRequest(c, "Missing Form Value MSISDN") 767 | } 768 | 769 | if len(reqMessageUpdate.MSGID) == 0 { 770 | return router.ResponseBadRequest(c, "Missing Form Value Message ID") 771 | } 772 | 773 | err = pkgWhatsApp.WhatsAppMessageDelete(c.Request().Context(), jid, reqMessageUpdate.RJID, reqMessageUpdate.MSGID) 774 | if err != nil { 775 | return router.ResponseInternalError(c, err.Error()) 776 | } 777 | 778 | return router.ResponseSuccess(c, "Successfully Delete Message") 779 | } 780 | -------------------------------------------------------------------------------- /pkg/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/env" 5 | ) 6 | 7 | var AuthBasicUsername string 8 | var AuthBasicPassword string 9 | 10 | var AuthJWTSecret string 11 | var AuthJWTExpiredHour int 12 | 13 | func init() { 14 | AuthBasicUsername, _ = env.GetEnvString("AUTH_BASIC_USERNAME") 15 | AuthBasicPassword, _ = env.GetEnvString("AUTH_BASIC_PASSWORD") 16 | 17 | AuthJWTSecret, _ = env.GetEnvString("AUTH_JWT_SECRET") 18 | AuthJWTExpiredHour, _ = env.GetEnvInt("AUTH_JWT_EXPIRED_HOUR") 19 | } 20 | -------------------------------------------------------------------------------- /pkg/auth/basic.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "encoding/base64" 5 | "io/ioutil" 6 | "strings" 7 | 8 | "github.com/labstack/echo/v4" 9 | 10 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" 11 | ) 12 | 13 | // BasicAuth Function as Midleware for Basic Authorization 14 | func BasicAuth() echo.MiddlewareFunc { 15 | return func(next echo.HandlerFunc) echo.HandlerFunc { 16 | return func(c echo.Context) error { 17 | // Parse HTTP Header Authorization 18 | authHeader := strings.SplitN(c.Request().Header.Get("Authorization"), " ", 2) 19 | 20 | // Check HTTP Header Authorization Section 21 | // Authorization Section Length Should Be 2 22 | // The First Authorization Section Should Be "Basic" 23 | if len(authHeader) != 2 || authHeader[0] != "Basic" { 24 | return router.ResponseAuthenticate(c) 25 | } 26 | 27 | // The Second Authorization Section Should Be The Credentials Payload 28 | // But We Should Decode it First From Base64 Encoding 29 | authPayload, err := base64.StdEncoding.DecodeString(authHeader[1]) 30 | if err != nil { 31 | return router.ResponseInternalError(c, "") 32 | } 33 | 34 | // Split Decoded Authorization Payload Into Username and Password Credentials 35 | authCredentials := strings.SplitN(string(authPayload), ":", 2) 36 | 37 | // Check Credentials Section 38 | // It Should Have 2 Section, Username and Password 39 | if len(authCredentials) != 2 { 40 | return router.ResponseBadRequest(c, "") 41 | } 42 | 43 | // Validate Authentication Password 44 | if authCredentials[1] != AuthBasicPassword { 45 | return router.ResponseBadRequest(c, "Invalid Authentication") 46 | } 47 | 48 | // Make Credentials to JSON Format 49 | authInformation := `{"username": "` + authCredentials[0] + `"}` 50 | 51 | // Rewrite Body Content With Credentials in JSON Format 52 | c.Request().Header.Set("Content-Type", "application/json") 53 | c.Request().Body = ioutil.NopCloser(strings.NewReader(authInformation)) 54 | 55 | // Call Next Handler Function With Current Request 56 | return next(c) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkg/env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "strconv" 7 | "strings" 8 | 9 | _ "github.com/joho/godotenv/autoload" 10 | ) 11 | 12 | func SanitizeEnv(envName string) (string, error) { 13 | if len(envName) == 0 { 14 | return "", errors.New("Environment Variable Name Should Not Empty") 15 | } 16 | 17 | retValue := strings.TrimSpace(os.Getenv(envName)) 18 | if len(retValue) == 0 { 19 | return "", errors.New("Environment Variable '" + envName + "' Has an Empty Value") 20 | } 21 | 22 | return retValue, nil 23 | } 24 | 25 | func GetEnvString(envName string) (string, error) { 26 | envValue, err := SanitizeEnv(envName) 27 | if err != nil { 28 | return "", err 29 | } 30 | 31 | return envValue, nil 32 | } 33 | 34 | func GetEnvBool(envName string) (bool, error) { 35 | envValue, err := SanitizeEnv(envName) 36 | if err != nil { 37 | return false, err 38 | } 39 | 40 | retValue, err := strconv.ParseBool(envValue) 41 | if err != nil { 42 | return false, err 43 | } 44 | 45 | return retValue, nil 46 | } 47 | 48 | func GetEnvInt(envName string) (int, error) { 49 | envValue, err := SanitizeEnv(envName) 50 | if err != nil { 51 | return 0, err 52 | } 53 | 54 | retValue, err := strconv.ParseInt(envValue, 0, 0) 55 | if err != nil { 56 | return 0, err 57 | } 58 | 59 | return int(retValue), nil 60 | } 61 | 62 | func GetEnvFloat32(envName string) (float32, error) { 63 | envValue, err := SanitizeEnv(envName) 64 | if err != nil { 65 | return 0, err 66 | } 67 | 68 | retValue, err := strconv.ParseFloat(envValue, 32) 69 | if err != nil { 70 | return 0, err 71 | } 72 | 73 | return float32(retValue), nil 74 | } 75 | 76 | func GetEnvFloat64(envName string) (float64, error) { 77 | envValue, err := SanitizeEnv(envName) 78 | if err != nil { 79 | return 0, err 80 | } 81 | 82 | retValue, err := strconv.ParseFloat(envValue, 64) 83 | if err != nil { 84 | return 0, err 85 | } 86 | 87 | return float64(retValue), nil 88 | } 89 | -------------------------------------------------------------------------------- /pkg/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/labstack/echo/v4" 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | var logger = logrus.New() 11 | 12 | func Print(c echo.Context) *logrus.Entry { 13 | logger.Formatter = &logrus.TextFormatter{ 14 | TimestampFormat: time.RFC3339, 15 | FullTimestamp: true, 16 | DisableColors: false, 17 | ForceColors: true, 18 | } 19 | 20 | if c == nil { 21 | return logger.WithFields(logrus.Fields{}) 22 | } 23 | 24 | return logger.WithFields(logrus.Fields{ 25 | "remote_ip": c.Request().RemoteAddr, 26 | "method": c.Request().Method, 27 | "uri": c.Request().URL.String(), 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/router/cache.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/labstack/echo/v4" 7 | 8 | cache "github.com/SporkHubr/echo-http-cache" 9 | "github.com/SporkHubr/echo-http-cache/adapter/memory" 10 | 11 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/log" 12 | ) 13 | 14 | func HttpCacheInMemory(cap int, ttl int) echo.MiddlewareFunc { 15 | // Check if Cache Capacity is Zero or Less 16 | if cap <= 0 { 17 | // Set Default Cache Capacity 18 | cap = 1000 19 | } 20 | 21 | // Check if Cache TTL is Zero or Less 22 | if ttl <= 0 { 23 | // Set Default Cache TTL 24 | ttl = 5 25 | } 26 | 27 | // Create New In-Memory Cache Adapter 28 | memcache, err := memory.NewAdapter( 29 | // Set In-Memory Cache Adapter Algorithm to LRU 30 | // and With Desired Capacity 31 | memory.AdapterWithAlgorithm(memory.LRU), 32 | memory.AdapterWithCapacity(cap), 33 | ) 34 | 35 | if err != nil { 36 | log.Print(nil).Error(err.Error()) 37 | return nil 38 | } 39 | 40 | // Create New Cache 41 | cache, err := cache.NewClient( 42 | // Set Cache Adapter with In-Memory Cache Adapter 43 | // and Set Cache TTL in Second(s) 44 | cache.ClientWithAdapter(memcache), 45 | cache.ClientWithTTL(time.Duration(ttl)*time.Second), 46 | ) 47 | 48 | if err != nil { 49 | log.Print(nil).Error(err.Error()) 50 | return nil 51 | } 52 | 53 | // Return Cache as Echo Middleware 54 | return cache.Middleware() 55 | } 56 | -------------------------------------------------------------------------------- /pkg/router/handler.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/labstack/echo/v4" 7 | ) 8 | 9 | func HttpErrorHandler(err error, c echo.Context) { 10 | report, _ := err.(*echo.HTTPError) 11 | 12 | response := &ResError{ 13 | Status: false, 14 | Code: report.Code, 15 | Error: fmt.Sprintf("%v", report.Message), 16 | } 17 | 18 | logError(c, response.Code, response.Error) 19 | c.JSON(response.Code, response) 20 | } 21 | -------------------------------------------------------------------------------- /pkg/router/middleware.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | func HttpRealIP() echo.MiddlewareFunc { 11 | return func(next echo.HandlerFunc) echo.HandlerFunc { 12 | return func(c echo.Context) error { 13 | if XForwardedFor := c.Request().Header.Get(http.CanonicalHeaderKey("X-Forwarded-For")); XForwardedFor != "" { 14 | dataIndex := strings.Index(XForwardedFor, ", ") 15 | if dataIndex == -1 { 16 | dataIndex = len(XForwardedFor) 17 | } 18 | 19 | c.Request().RemoteAddr = XForwardedFor[:dataIndex] 20 | } else if XRealIP := c.Request().Header.Get(http.CanonicalHeaderKey("X-Real-IP")); XRealIP != "" { 21 | c.Request().RemoteAddr = XRealIP 22 | } 23 | 24 | return next(c) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pkg/router/response.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/labstack/echo/v4" 9 | 10 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/log" 11 | ) 12 | 13 | type ResSuccess struct { 14 | Status bool `json:"status"` 15 | Code int `json:"code"` 16 | Message string `json:"message"` 17 | } 18 | 19 | type ResSuccessWithData struct { 20 | Status bool `json:"status"` 21 | Code int `json:"code"` 22 | Message string `json:"message"` 23 | Data interface{} `json:"data"` 24 | } 25 | 26 | type ResError struct { 27 | Status bool `json:"status"` 28 | Code int `json:"code"` 29 | Error string `json:"error"` 30 | } 31 | 32 | func logSuccess(c echo.Context, code int, message string) { 33 | statusMessage := http.StatusText(code) 34 | 35 | if statusMessage == message || c.Request().RequestURI == BaseURL { 36 | log.Print(c).Info(fmt.Sprintf("%d %v", code, statusMessage)) 37 | } else { 38 | log.Print(c).Info(fmt.Sprintf("%d %v", code, message)) 39 | } 40 | } 41 | 42 | func logError(c echo.Context, code int, message string) { 43 | statusMessage := http.StatusText(code) 44 | 45 | if statusMessage == message { 46 | log.Print(c).Error(fmt.Sprintf("%d %v", code, statusMessage)) 47 | } else { 48 | log.Print(c).Error(fmt.Sprintf("%d %v", code, message)) 49 | } 50 | } 51 | 52 | func ResponseSuccess(c echo.Context, message string) error { 53 | var response ResSuccess 54 | 55 | response.Status = true 56 | response.Code = http.StatusOK 57 | 58 | if strings.TrimSpace(message) == "" { 59 | message = http.StatusText(response.Code) 60 | } 61 | response.Message = message 62 | 63 | logSuccess(c, response.Code, response.Message) 64 | return c.JSON(response.Code, response) 65 | } 66 | 67 | func ResponseSuccessWithData(c echo.Context, message string, data interface{}) error { 68 | var response ResSuccessWithData 69 | 70 | response.Status = true 71 | response.Code = http.StatusOK 72 | 73 | if strings.TrimSpace(message) == "" { 74 | message = http.StatusText(response.Code) 75 | } 76 | response.Message = message 77 | response.Data = data 78 | 79 | logSuccess(c, response.Code, response.Message) 80 | return c.JSON(response.Code, response) 81 | } 82 | 83 | func ResponseSuccessWithHTML(c echo.Context, html string) error { 84 | logSuccess(c, http.StatusOK, http.StatusText(http.StatusOK)) 85 | return c.HTML(http.StatusOK, html) 86 | } 87 | 88 | func ResponseCreated(c echo.Context, message string) error { 89 | var response ResSuccess 90 | 91 | response.Status = true 92 | response.Code = http.StatusCreated 93 | 94 | if strings.TrimSpace(message) == "" { 95 | message = http.StatusText(response.Code) 96 | } 97 | response.Message = message 98 | 99 | logSuccess(c, response.Code, response.Message) 100 | return c.JSON(response.Code, response) 101 | } 102 | 103 | func ResponseNoContent(c echo.Context) error { 104 | return c.NoContent(http.StatusNoContent) 105 | } 106 | 107 | func ResponseNotFound(c echo.Context, message string) error { 108 | var response ResError 109 | 110 | response.Status = false 111 | response.Code = http.StatusNotFound 112 | 113 | if strings.TrimSpace(message) == "" { 114 | message = http.StatusText(response.Code) 115 | } 116 | response.Error = message 117 | 118 | logError(c, response.Code, response.Error) 119 | return c.JSON(response.Code, response) 120 | } 121 | 122 | func ResponseAuthenticate(c echo.Context) error { 123 | c.Response().Header().Set("WWW-Authenticate", `Basic realm="Authentication Required"`) 124 | return ResponseUnauthorized(c, "") 125 | } 126 | 127 | func ResponseUnauthorized(c echo.Context, message string) error { 128 | var response ResError 129 | 130 | response.Status = false 131 | response.Code = http.StatusUnauthorized 132 | 133 | if strings.TrimSpace(message) == "" { 134 | message = http.StatusText(response.Code) 135 | } 136 | response.Error = message 137 | 138 | logError(c, response.Code, response.Error) 139 | return c.JSON(response.Code, response) 140 | } 141 | 142 | func ResponseBadRequest(c echo.Context, message string) error { 143 | var response ResError 144 | 145 | response.Status = false 146 | response.Code = http.StatusBadRequest 147 | 148 | if strings.TrimSpace(message) == "" { 149 | message = http.StatusText(response.Code) 150 | } 151 | response.Error = message 152 | 153 | logError(c, response.Code, response.Error) 154 | return c.JSON(response.Code, response) 155 | } 156 | 157 | func ResponseInternalError(c echo.Context, message string) error { 158 | var response ResError 159 | 160 | response.Status = false 161 | response.Code = http.StatusInternalServerError 162 | 163 | if strings.TrimSpace(message) == "" { 164 | message = http.StatusText(response.Code) 165 | } 166 | response.Error = message 167 | 168 | logError(c, response.Code, response.Error) 169 | return c.JSON(response.Code, response) 170 | } 171 | 172 | func ResponseBadGateway(c echo.Context, message string) error { 173 | var response ResError 174 | 175 | response.Status = false 176 | response.Code = http.StatusBadGateway 177 | 178 | if strings.TrimSpace(message) == "" { 179 | message = http.StatusText(response.Code) 180 | } 181 | response.Error = message 182 | 183 | logError(c, response.Code, response.Error) 184 | return c.JSON(response.Code, response) 185 | } 186 | -------------------------------------------------------------------------------- /pkg/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/env" 5 | ) 6 | 7 | var BaseURL, CORSOrigin, BodyLimit string 8 | var GZipLevel int 9 | var CacheCapacity, CacheTTLSeconds int 10 | 11 | func init() { 12 | var err error 13 | 14 | BaseURL, err = env.GetEnvString("HTTP_BASE_URL") 15 | if err != nil { 16 | BaseURL = "/" 17 | } 18 | 19 | CORSOrigin, err = env.GetEnvString("HTTP_CORS_ORIGIN") 20 | if err != nil { 21 | CORSOrigin = "*" 22 | } 23 | 24 | BodyLimit, err = env.GetEnvString("HTTP_BODY_LIMIT_SIZE") 25 | if err != nil { 26 | BodyLimit = "8M" 27 | } 28 | 29 | GZipLevel, err = env.GetEnvInt("HTTP_GZIP_LEVEL") 30 | if err != nil { 31 | GZipLevel = 1 32 | } 33 | 34 | CacheCapacity, err = env.GetEnvInt("HTTP_CACHE_CAPACITY") 35 | if err != nil { 36 | CacheCapacity = 100 37 | } 38 | 39 | CacheTTLSeconds, err = env.GetEnvInt("HTTP_CACHE_TTL_SECONDS") 40 | if err != nil { 41 | CacheTTLSeconds = 5 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/whatsapp/drivers.go: -------------------------------------------------------------------------------- 1 | package whatsapp 2 | 3 | import ( 4 | _ "github.com/lib/pq" 5 | _ "modernc.org/sqlite" 6 | ) 7 | -------------------------------------------------------------------------------- /pkg/whatsapp/version.go: -------------------------------------------------------------------------------- 1 | package whatsapp 2 | 3 | type clientVersion struct { 4 | Major int 5 | Minor int 6 | Patch int 7 | } 8 | 9 | var version clientVersion 10 | -------------------------------------------------------------------------------- /pkg/whatsapp/whatsapp.go: -------------------------------------------------------------------------------- 1 | package whatsapp 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/base64" 7 | "errors" 8 | "fmt" 9 | "net/http" 10 | "runtime" 11 | "strings" 12 | "time" 13 | 14 | "github.com/PuerkitoBio/goquery" 15 | "github.com/forPelevin/gomoji" 16 | webp "github.com/nickalie/go-webpbin" 17 | "github.com/rivo/uniseg" 18 | "github.com/sunshineplan/imgconv" 19 | 20 | qrCode "github.com/skip2/go-qrcode" 21 | "google.golang.org/protobuf/proto" 22 | 23 | "go.mau.fi/whatsmeow" 24 | wabin "go.mau.fi/whatsmeow/binary" 25 | "go.mau.fi/whatsmeow/proto/waCommon" 26 | "go.mau.fi/whatsmeow/proto/waCompanionReg" 27 | "go.mau.fi/whatsmeow/proto/waE2E" 28 | "go.mau.fi/whatsmeow/store" 29 | "go.mau.fi/whatsmeow/store/sqlstore" 30 | "go.mau.fi/whatsmeow/types" 31 | 32 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/env" 33 | "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/log" 34 | ) 35 | 36 | var WhatsAppDatastore *sqlstore.Container 37 | var WhatsAppClient = make(map[string]*whatsmeow.Client) 38 | 39 | var ( 40 | WhatsAppClientProxyURL string 41 | ) 42 | 43 | func init() { 44 | var err error 45 | 46 | dbType, err := env.GetEnvString("WHATSAPP_DATASTORE_TYPE") 47 | if err != nil { 48 | log.Print(nil).Fatal("Error Parse Environment Variable for WhatsApp Client Datastore Type") 49 | } 50 | 51 | dbURI, err := env.GetEnvString("WHATSAPP_DATASTORE_URI") 52 | if err != nil { 53 | log.Print(nil).Fatal("Error Parse Environment Variable for WhatsApp Client Datastore URI") 54 | } 55 | 56 | datastore, err := sqlstore.New(dbType, dbURI, nil) 57 | if err != nil { 58 | log.Print(nil).Fatal("Error Connect WhatsApp Client Datastore") 59 | } 60 | 61 | WhatsAppClientProxyURL, _ = env.GetEnvString("WHATSAPP_CLIENT_PROXY_URL") 62 | 63 | WhatsAppDatastore = datastore 64 | } 65 | 66 | func WhatsAppInitClient(device *store.Device, jid string) { 67 | var err error 68 | wabin.IndentXML = true 69 | 70 | if WhatsAppClient[jid] == nil { 71 | if device == nil { 72 | // Initialize New WhatsApp Client Device in Datastore 73 | device = WhatsAppDatastore.NewDevice() 74 | } 75 | 76 | // Set Client Properties 77 | store.DeviceProps.Os = proto.String(WhatsAppGetUserOS()) 78 | store.DeviceProps.PlatformType = WhatsAppGetUserAgent("chrome").Enum() 79 | store.DeviceProps.RequireFullSync = proto.Bool(false) 80 | 81 | // Set Client Versions 82 | version.Major, err = env.GetEnvInt("WHATSAPP_VERSION_MAJOR") 83 | if err == nil { 84 | store.DeviceProps.Version.Primary = proto.Uint32(uint32(version.Major)) 85 | } 86 | version.Minor, err = env.GetEnvInt("WHATSAPP_VERSION_MINOR") 87 | if err == nil { 88 | store.DeviceProps.Version.Secondary = proto.Uint32(uint32(version.Minor)) 89 | } 90 | version.Patch, err = env.GetEnvInt("WHATSAPP_VERSION_PATCH") 91 | if err == nil { 92 | store.DeviceProps.Version.Tertiary = proto.Uint32(uint32(version.Patch)) 93 | } 94 | 95 | // Initialize New WhatsApp Client 96 | // And Save it to The Map 97 | WhatsAppClient[jid] = whatsmeow.NewClient(device, nil) 98 | 99 | // Set WhatsApp Client Proxy Address if Proxy URL is Provided 100 | if len(WhatsAppClientProxyURL) > 0 { 101 | WhatsAppClient[jid].SetProxyAddress(WhatsAppClientProxyURL) 102 | } 103 | 104 | // Set WhatsApp Client Auto Reconnect 105 | WhatsAppClient[jid].EnableAutoReconnect = true 106 | 107 | // Set WhatsApp Client Auto Trust Identity 108 | WhatsAppClient[jid].AutoTrustIdentity = true 109 | } 110 | } 111 | 112 | func WhatsAppGetUserAgent(agentType string) waCompanionReg.DeviceProps_PlatformType { 113 | switch strings.ToLower(agentType) { 114 | case "desktop": 115 | return waCompanionReg.DeviceProps_DESKTOP 116 | case "mac": 117 | return waCompanionReg.DeviceProps_CATALINA 118 | case "android": 119 | return waCompanionReg.DeviceProps_ANDROID_AMBIGUOUS 120 | case "android-phone": 121 | return waCompanionReg.DeviceProps_ANDROID_PHONE 122 | case "andorid-tablet": 123 | return waCompanionReg.DeviceProps_ANDROID_TABLET 124 | case "ios-phone": 125 | return waCompanionReg.DeviceProps_IOS_PHONE 126 | case "ios-catalyst": 127 | return waCompanionReg.DeviceProps_IOS_CATALYST 128 | case "ipad": 129 | return waCompanionReg.DeviceProps_IPAD 130 | case "wearos": 131 | return waCompanionReg.DeviceProps_WEAR_OS 132 | case "ie": 133 | return waCompanionReg.DeviceProps_IE 134 | case "edge": 135 | return waCompanionReg.DeviceProps_EDGE 136 | case "chrome": 137 | return waCompanionReg.DeviceProps_CHROME 138 | case "safari": 139 | return waCompanionReg.DeviceProps_SAFARI 140 | case "firefox": 141 | return waCompanionReg.DeviceProps_FIREFOX 142 | case "opera": 143 | return waCompanionReg.DeviceProps_OPERA 144 | case "uwp": 145 | return waCompanionReg.DeviceProps_UWP 146 | case "aloha": 147 | return waCompanionReg.DeviceProps_ALOHA 148 | case "tv-tcl": 149 | return waCompanionReg.DeviceProps_TCL_TV 150 | default: 151 | return waCompanionReg.DeviceProps_UNKNOWN 152 | } 153 | } 154 | 155 | func WhatsAppGetUserOS() string { 156 | switch runtime.GOOS { 157 | case "windows": 158 | return "Windows" 159 | case "darwin": 160 | return "macOS" 161 | default: 162 | return "Linux" 163 | } 164 | } 165 | 166 | func WhatsAppGenerateQR(qrChan <-chan whatsmeow.QRChannelItem) (string, int) { 167 | qrChanCode := make(chan string) 168 | qrChanTimeout := make(chan int) 169 | 170 | // Get QR Code Data and Timeout 171 | go func() { 172 | for evt := range qrChan { 173 | if evt.Event == "code" { 174 | qrChanCode <- evt.Code 175 | qrChanTimeout <- int(evt.Timeout.Seconds()) 176 | } 177 | } 178 | }() 179 | 180 | // Generate QR Code Data to PNG Image 181 | qrTemp := <-qrChanCode 182 | qrPNG, _ := qrCode.Encode(qrTemp, qrCode.Medium, 256) 183 | 184 | // Return QR Code PNG in Base64 Format and Timeout Information 185 | return base64.StdEncoding.EncodeToString(qrPNG), <-qrChanTimeout 186 | } 187 | 188 | func WhatsAppLogin(jid string) (string, int, error) { 189 | if WhatsAppClient[jid] != nil { 190 | // Make Sure WebSocket Connection is Disconnected 191 | WhatsAppClient[jid].Disconnect() 192 | 193 | if WhatsAppClient[jid].Store.ID == nil { 194 | // Device ID is not Exist 195 | // Generate QR Code 196 | qrChanGenerate, _ := WhatsAppClient[jid].GetQRChannel(context.Background()) 197 | 198 | // Connect WebSocket while Initialize QR Code Data to be Sent 199 | err := WhatsAppClient[jid].Connect() 200 | if err != nil { 201 | return "", 0, err 202 | } 203 | 204 | // Get Generated QR Code and Timeout Information 205 | qrImage, qrTimeout := WhatsAppGenerateQR(qrChanGenerate) 206 | 207 | // Return QR Code in Base64 Format and Timeout Information 208 | return "data:image/png;base64," + qrImage, qrTimeout, nil 209 | } else { 210 | // Device ID is Exist 211 | // Reconnect WebSocket 212 | err := WhatsAppReconnect(jid) 213 | if err != nil { 214 | return "", 0, err 215 | } 216 | 217 | return "WhatsApp Client is Reconnected", 0, nil 218 | } 219 | } 220 | 221 | // Return Error WhatsApp Client is not Valid 222 | return "", 0, errors.New("WhatsApp Client is not Valid") 223 | } 224 | 225 | func WhatsAppLoginPair(jid string) (string, int, error) { 226 | if WhatsAppClient[jid] != nil { 227 | // Make Sure WebSocket Connection is Disconnected 228 | WhatsAppClient[jid].Disconnect() 229 | 230 | if WhatsAppClient[jid].Store.ID == nil { 231 | // Connect WebSocket while also Requesting Pairing Code 232 | err := WhatsAppClient[jid].Connect() 233 | if err != nil { 234 | return "", 0, err 235 | } 236 | 237 | // Request Pairing Code 238 | code, err := WhatsAppClient[jid].PairPhone(jid, true, whatsmeow.PairClientChrome, "Chrome ("+WhatsAppGetUserOS()+")") 239 | if err != nil { 240 | return "", 0, err 241 | } 242 | 243 | return code, 160, nil 244 | } else { 245 | // Device ID is Exist 246 | // Reconnect WebSocket 247 | err := WhatsAppReconnect(jid) 248 | if err != nil { 249 | return "", 0, err 250 | } 251 | 252 | return "WhatsApp Client is Reconnected", 0, nil 253 | } 254 | } 255 | 256 | // Return Error WhatsApp Client is not Valid 257 | return "", 0, errors.New("WhatsApp Client is not Valid") 258 | } 259 | 260 | func WhatsAppReconnect(jid string) error { 261 | if WhatsAppClient[jid] != nil { 262 | // Make Sure WebSocket Connection is Disconnected 263 | WhatsAppClient[jid].Disconnect() 264 | 265 | // Make Sure Store ID is not Empty 266 | // To do Reconnection 267 | if WhatsAppClient[jid] != nil { 268 | err := WhatsAppClient[jid].Connect() 269 | if err != nil { 270 | return err 271 | } 272 | 273 | return nil 274 | } 275 | 276 | return errors.New("WhatsApp Client Store ID is Empty, Please Re-Login and Scan QR Code Again") 277 | } 278 | 279 | return errors.New("WhatsApp Client is not Valid") 280 | } 281 | 282 | func WhatsAppLogout(jid string) error { 283 | if WhatsAppClient[jid] != nil { 284 | // Make Sure Store ID is not Empty 285 | if WhatsAppClient[jid] != nil { 286 | var err error 287 | 288 | // Set WhatsApp Client Presence to Unavailable 289 | WhatsAppPresence(jid, false) 290 | 291 | // Logout WhatsApp Client and Disconnect from WebSocket 292 | err = WhatsAppClient[jid].Logout() 293 | if err != nil { 294 | // Force Disconnect 295 | WhatsAppClient[jid].Disconnect() 296 | 297 | // Manually Delete Device from Datastore Store 298 | err = WhatsAppClient[jid].Store.Delete() 299 | if err != nil { 300 | return err 301 | } 302 | } 303 | 304 | // Free WhatsApp Client Map 305 | WhatsAppClient[jid] = nil 306 | delete(WhatsAppClient, jid) 307 | 308 | return nil 309 | } 310 | 311 | return errors.New("WhatsApp Client Store ID is Empty, Please Re-Login and Scan QR Code Again") 312 | } 313 | 314 | // Return Error WhatsApp Client is not Valid 315 | return errors.New("WhatsApp Client is not Valid") 316 | } 317 | 318 | func WhatsAppIsClientOK(jid string) error { 319 | // Make Sure WhatsApp Client is Connected 320 | if !WhatsAppClient[jid].IsConnected() { 321 | return errors.New("WhatsApp Client is not Connected") 322 | } 323 | 324 | // Make Sure WhatsApp Client is Logged In 325 | if !WhatsAppClient[jid].IsLoggedIn() { 326 | return errors.New("WhatsApp Client is not Logged In") 327 | } 328 | 329 | return nil 330 | } 331 | 332 | func WhatsAppGetJID(jid string, id string) types.JID { 333 | if WhatsAppClient[jid] != nil { 334 | var ids []string 335 | 336 | ids = append(ids, "+"+id) 337 | infos, err := WhatsAppClient[jid].IsOnWhatsApp(ids) 338 | if err == nil { 339 | // If WhatsApp ID is Registered Then 340 | // Return ID Information 341 | if infos[0].IsIn { 342 | return infos[0].JID 343 | } 344 | } 345 | } 346 | 347 | // Return Empty ID Information 348 | return types.EmptyJID 349 | } 350 | 351 | func WhatsAppCheckJID(jid string, id string) (types.JID, error) { 352 | if WhatsAppClient[jid] != nil { 353 | // Compose New Remote JID 354 | remoteJID := WhatsAppComposeJID(id) 355 | if remoteJID.Server != types.GroupServer { 356 | // Validate JID if Remote JID is not Group JID 357 | if WhatsAppGetJID(jid, remoteJID.String()).IsEmpty() { 358 | return types.EmptyJID, errors.New("WhatsApp Personal ID is Not Registered") 359 | } 360 | } 361 | 362 | // Return Remote ID Information 363 | return remoteJID, nil 364 | } 365 | 366 | // Return Empty ID Information 367 | return types.EmptyJID, nil 368 | } 369 | 370 | func WhatsAppComposeJID(id string) types.JID { 371 | // Decompose WhatsApp ID First Before Recomposing 372 | id = WhatsAppDecomposeJID(id) 373 | 374 | // Check if ID is Group or Not By Detecting '-' for Old Group ID 375 | // Or By ID Length That Should be 18 Digits or More 376 | if strings.ContainsRune(id, '-') || len(id) >= 18 { 377 | // Return New Group User JID 378 | return types.NewJID(id, types.GroupServer) 379 | } 380 | 381 | // Return New Standard User JID 382 | return types.NewJID(id, types.DefaultUserServer) 383 | } 384 | 385 | func WhatsAppDecomposeJID(id string) string { 386 | // Check if WhatsApp ID Contains '@' Symbol 387 | if strings.ContainsRune(id, '@') { 388 | // Split WhatsApp ID Based on '@' Symbol 389 | // and Get Only The First Section Before The Symbol 390 | buffers := strings.Split(id, "@") 391 | id = buffers[0] 392 | } 393 | 394 | // Check if WhatsApp ID First Character is '+' Symbol 395 | if id[0] == '+' { 396 | // Remove '+' Symbol from WhatsApp ID 397 | id = id[1:] 398 | } 399 | 400 | return id 401 | } 402 | 403 | func WhatsAppPresence(jid string, isAvailable bool) { 404 | if isAvailable { 405 | _ = WhatsAppClient[jid].SendPresence(types.PresenceAvailable) 406 | } else { 407 | _ = WhatsAppClient[jid].SendPresence(types.PresenceUnavailable) 408 | } 409 | } 410 | 411 | func WhatsAppComposeStatus(jid string, rjid types.JID, isComposing bool, isAudio bool) { 412 | // Set Compose Status 413 | var typeCompose types.ChatPresence 414 | if isComposing { 415 | typeCompose = types.ChatPresenceComposing 416 | } else { 417 | typeCompose = types.ChatPresencePaused 418 | } 419 | 420 | // Set Compose Media Audio (Recording) or Text (Typing) 421 | var typeComposeMedia types.ChatPresenceMedia 422 | if isAudio { 423 | typeComposeMedia = types.ChatPresenceMediaAudio 424 | } else { 425 | typeComposeMedia = types.ChatPresenceMediaText 426 | } 427 | 428 | // Send Chat Compose Status 429 | _ = WhatsAppClient[jid].SendChatPresence(rjid, typeCompose, typeComposeMedia) 430 | } 431 | 432 | func WhatsAppCheckRegistered(jid string, id string) error { 433 | if WhatsAppClient[jid] != nil { 434 | var err error 435 | 436 | // Make Sure WhatsApp Client is OK 437 | err = WhatsAppIsClientOK(jid) 438 | if err != nil { 439 | return err 440 | } 441 | 442 | // Make Sure WhatsApp ID is Registered 443 | remoteJID, err := WhatsAppCheckJID(jid, id) 444 | if err != nil { 445 | return err 446 | } 447 | 448 | // Make Sure WhatsApp ID is Not Empty or It is Not Group ID 449 | if remoteJID.IsEmpty() || remoteJID.Server == types.GroupServer { 450 | return errors.New("WhatsApp Personal ID is Not Registered") 451 | } 452 | 453 | return nil 454 | } 455 | 456 | // Return Error WhatsApp Client is not Valid 457 | return errors.New("WhatsApp Client is not Valid") 458 | } 459 | 460 | func WhatsAppSendText(ctx context.Context, jid string, rjid string, message string) (string, error) { 461 | if WhatsAppClient[jid] != nil { 462 | var err error 463 | 464 | // Make Sure WhatsApp Client is OK 465 | err = WhatsAppIsClientOK(jid) 466 | if err != nil { 467 | return "", err 468 | } 469 | 470 | // Make Sure WhatsApp ID is Registered 471 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 472 | if err != nil { 473 | return "", err 474 | } 475 | 476 | // Set Chat Presence 477 | WhatsAppPresence(jid, true) 478 | WhatsAppComposeStatus(jid, remoteJID, true, false) 479 | defer func() { 480 | WhatsAppComposeStatus(jid, remoteJID, false, false) 481 | WhatsAppPresence(jid, false) 482 | }() 483 | 484 | // Compose WhatsApp Proto 485 | msgExtra := whatsmeow.SendRequestExtra{ 486 | ID: WhatsAppClient[jid].GenerateMessageID(), 487 | } 488 | msgContent := &waE2E.Message{ 489 | Conversation: proto.String(message), 490 | } 491 | 492 | // Send WhatsApp Message Proto 493 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgContent, msgExtra) 494 | if err != nil { 495 | return "", err 496 | } 497 | 498 | return msgExtra.ID, nil 499 | } 500 | 501 | // Return Error WhatsApp Client is not Valid 502 | return "", errors.New("WhatsApp Client is not Valid") 503 | } 504 | 505 | func WhatsAppSendLocation(ctx context.Context, jid string, rjid string, latitude float64, longitude float64) (string, error) { 506 | if WhatsAppClient[jid] != nil { 507 | var err error 508 | 509 | // Make Sure WhatsApp Client is OK 510 | err = WhatsAppIsClientOK(jid) 511 | if err != nil { 512 | return "", err 513 | } 514 | 515 | // Make Sure WhatsApp ID is Registered 516 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 517 | if err != nil { 518 | return "", err 519 | } 520 | 521 | // Set Chat Presence 522 | WhatsAppPresence(jid, true) 523 | WhatsAppComposeStatus(jid, remoteJID, true, false) 524 | defer func() { 525 | WhatsAppComposeStatus(jid, remoteJID, false, false) 526 | WhatsAppPresence(jid, false) 527 | }() 528 | 529 | // Compose WhatsApp Proto 530 | msgExtra := whatsmeow.SendRequestExtra{ 531 | ID: WhatsAppClient[jid].GenerateMessageID(), 532 | } 533 | msgContent := &waE2E.Message{ 534 | LocationMessage: &waE2E.LocationMessage{ 535 | DegreesLatitude: proto.Float64(latitude), 536 | DegreesLongitude: proto.Float64(longitude), 537 | }, 538 | } 539 | 540 | // Send WhatsApp Message Proto 541 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgContent, msgExtra) 542 | if err != nil { 543 | return "", err 544 | } 545 | 546 | return msgExtra.ID, nil 547 | } 548 | 549 | // Return Error WhatsApp Client is not Valid 550 | return "", errors.New("WhatsApp Client is not Valid") 551 | } 552 | 553 | func WhatsAppSendDocument(ctx context.Context, jid string, rjid string, fileBytes []byte, fileType string, fileName string) (string, error) { 554 | if WhatsAppClient[jid] != nil { 555 | var err error 556 | 557 | // Make Sure WhatsApp Client is OK 558 | err = WhatsAppIsClientOK(jid) 559 | if err != nil { 560 | return "", err 561 | } 562 | 563 | // Make Sure WhatsApp ID is Registered 564 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 565 | if err != nil { 566 | return "", err 567 | } 568 | 569 | // Set Chat Presence 570 | WhatsAppPresence(jid, true) 571 | WhatsAppComposeStatus(jid, remoteJID, true, false) 572 | defer func() { 573 | WhatsAppComposeStatus(jid, remoteJID, false, false) 574 | WhatsAppPresence(jid, false) 575 | }() 576 | 577 | // Upload File to WhatsApp Storage Server 578 | fileUploaded, err := WhatsAppClient[jid].Upload(ctx, fileBytes, whatsmeow.MediaDocument) 579 | if err != nil { 580 | return "", errors.New("Error While Uploading Media to WhatsApp Server") 581 | } 582 | 583 | // Compose WhatsApp Proto 584 | msgExtra := whatsmeow.SendRequestExtra{ 585 | ID: WhatsAppClient[jid].GenerateMessageID(), 586 | } 587 | msgContent := &waE2E.Message{ 588 | DocumentMessage: &waE2E.DocumentMessage{ 589 | URL: proto.String(fileUploaded.URL), 590 | DirectPath: proto.String(fileUploaded.DirectPath), 591 | Mimetype: proto.String(fileType), 592 | Title: proto.String(fileName), 593 | FileName: proto.String(fileName), 594 | FileLength: proto.Uint64(fileUploaded.FileLength), 595 | FileSHA256: fileUploaded.FileSHA256, 596 | FileEncSHA256: fileUploaded.FileEncSHA256, 597 | MediaKey: fileUploaded.MediaKey, 598 | }, 599 | } 600 | 601 | // Send WhatsApp Message Proto 602 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgContent, msgExtra) 603 | if err != nil { 604 | return "", err 605 | } 606 | 607 | return msgExtra.ID, nil 608 | } 609 | 610 | // Return Error WhatsApp Client is not Valid 611 | return "", errors.New("WhatsApp Client is not Valid") 612 | } 613 | 614 | func WhatsAppSendImage(ctx context.Context, jid string, rjid string, imageBytes []byte, imageType string, imageCaption string, isViewOnce bool) (string, error) { 615 | if WhatsAppClient[jid] != nil { 616 | var err error 617 | 618 | // Make Sure WhatsApp Client is OK 619 | err = WhatsAppIsClientOK(jid) 620 | if err != nil { 621 | return "", err 622 | } 623 | 624 | // Make Sure WhatsApp ID is Registered 625 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 626 | if err != nil { 627 | return "", err 628 | } 629 | 630 | // Set Chat Presence 631 | WhatsAppPresence(jid, true) 632 | WhatsAppComposeStatus(jid, remoteJID, true, false) 633 | defer func() { 634 | WhatsAppComposeStatus(jid, remoteJID, false, false) 635 | WhatsAppPresence(jid, false) 636 | }() 637 | 638 | // Issue #7 Old Version Client Cannot Render WebP Format 639 | // If MIME Type is "image/webp" Then Convert it as PNG 640 | isWhatsAppImageConvertWebP, err := env.GetEnvBool("WHATSAPP_MEDIA_IMAGE_CONVERT_WEBP") 641 | if err != nil { 642 | isWhatsAppImageConvertWebP = false 643 | } 644 | 645 | if imageType == "image/webp" && isWhatsAppImageConvertWebP { 646 | imgConvDecode, err := imgconv.Decode(bytes.NewReader(imageBytes)) 647 | if err != nil { 648 | return "", errors.New("Error While Decoding Convert Image Stream") 649 | } 650 | 651 | imgConvEncode := new(bytes.Buffer) 652 | 653 | err = imgconv.Write(imgConvEncode, imgConvDecode, &imgconv.FormatOption{Format: imgconv.PNG}) 654 | if err != nil { 655 | return "", errors.New("Error While Encoding Convert Image Stream") 656 | } 657 | 658 | imageBytes = imgConvEncode.Bytes() 659 | imageType = "image/png" 660 | } 661 | 662 | // If WhatsApp Media Compression Enabled 663 | // Then Resize The Image to Width 1024px and Preserve Aspect Ratio 664 | isWhatsAppImageCompression, err := env.GetEnvBool("WHATSAPP_MEDIA_IMAGE_COMPRESSION") 665 | if err != nil { 666 | isWhatsAppImageCompression = false 667 | } 668 | 669 | if isWhatsAppImageCompression { 670 | imgResizeDecode, err := imgconv.Decode(bytes.NewReader(imageBytes)) 671 | if err != nil { 672 | return "", errors.New("Error While Decoding Resize Image Stream") 673 | } 674 | 675 | imgResizeEncode := new(bytes.Buffer) 676 | 677 | err = imgconv.Write(imgResizeEncode, 678 | imgconv.Resize(imgResizeDecode, &imgconv.ResizeOption{Width: 1024}), 679 | &imgconv.FormatOption{}) 680 | 681 | if err != nil { 682 | return "", errors.New("Error While Encoding Resize Image Stream") 683 | } 684 | 685 | imageBytes = imgResizeEncode.Bytes() 686 | } 687 | 688 | // Creating Image JPEG Thumbnail 689 | // With Permanent Width 640px and Preserve Aspect Ratio 690 | imgThumbDecode, err := imgconv.Decode(bytes.NewReader(imageBytes)) 691 | if err != nil { 692 | return "", errors.New("Error While Decoding Thumbnail Image Stream") 693 | } 694 | 695 | imgThumbEncode := new(bytes.Buffer) 696 | 697 | err = imgconv.Write(imgThumbEncode, 698 | imgconv.Resize(imgThumbDecode, &imgconv.ResizeOption{Width: 72}), 699 | &imgconv.FormatOption{Format: imgconv.JPEG}) 700 | 701 | if err != nil { 702 | return "", errors.New("Error While Encoding Thumbnail Image Stream") 703 | } 704 | 705 | // Upload Image to WhatsApp Storage Server 706 | imageUploaded, err := WhatsAppClient[jid].Upload(ctx, imageBytes, whatsmeow.MediaImage) 707 | if err != nil { 708 | return "", errors.New("Error While Uploading Media to WhatsApp Server") 709 | } 710 | 711 | // Upload Image Thumbnail to WhatsApp Storage Server 712 | imageThumbUploaded, err := WhatsAppClient[jid].Upload(ctx, imgThumbEncode.Bytes(), whatsmeow.MediaLinkThumbnail) 713 | if err != nil { 714 | return "", errors.New("Error while Uploading Image Thumbnail to WhatsApp Server") 715 | } 716 | 717 | // Compose WhatsApp Proto 718 | msgExtra := whatsmeow.SendRequestExtra{ 719 | ID: WhatsAppClient[jid].GenerateMessageID(), 720 | } 721 | msgContent := &waE2E.Message{ 722 | ImageMessage: &waE2E.ImageMessage{ 723 | URL: proto.String(imageUploaded.URL), 724 | DirectPath: proto.String(imageUploaded.DirectPath), 725 | Mimetype: proto.String(imageType), 726 | Caption: proto.String(imageCaption), 727 | FileLength: proto.Uint64(imageUploaded.FileLength), 728 | FileSHA256: imageUploaded.FileSHA256, 729 | FileEncSHA256: imageUploaded.FileEncSHA256, 730 | MediaKey: imageUploaded.MediaKey, 731 | JPEGThumbnail: imgThumbEncode.Bytes(), 732 | ThumbnailDirectPath: &imageThumbUploaded.DirectPath, 733 | ThumbnailSHA256: imageThumbUploaded.FileSHA256, 734 | ThumbnailEncSHA256: imageThumbUploaded.FileEncSHA256, 735 | ViewOnce: proto.Bool(isViewOnce), 736 | }, 737 | } 738 | 739 | // Send WhatsApp Message Proto 740 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgContent, msgExtra) 741 | if err != nil { 742 | return "", err 743 | } 744 | 745 | return msgExtra.ID, nil 746 | } 747 | 748 | // Return Error WhatsApp Client is not Valid 749 | return "", errors.New("WhatsApp Client is not Valid") 750 | } 751 | 752 | func WhatsAppSendAudio(ctx context.Context, jid string, rjid string, audioBytes []byte, audioType string) (string, error) { 753 | if WhatsAppClient[jid] != nil { 754 | var err error 755 | 756 | // Make Sure WhatsApp Client is OK 757 | err = WhatsAppIsClientOK(jid) 758 | if err != nil { 759 | return "", err 760 | } 761 | 762 | // Make Sure WhatsApp ID is Registered 763 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 764 | if err != nil { 765 | return "", err 766 | } 767 | 768 | // Set Chat Presence 769 | WhatsAppComposeStatus(jid, remoteJID, true, true) 770 | defer WhatsAppComposeStatus(jid, remoteJID, false, true) 771 | 772 | // Upload Audio to WhatsApp Storage Server 773 | audioUploaded, err := WhatsAppClient[jid].Upload(ctx, audioBytes, whatsmeow.MediaAudio) 774 | if err != nil { 775 | return "", errors.New("Error While Uploading Media to WhatsApp Server") 776 | } 777 | 778 | // Compose WhatsApp Proto 779 | msgExtra := whatsmeow.SendRequestExtra{ 780 | ID: WhatsAppClient[jid].GenerateMessageID(), 781 | } 782 | msgContent := &waE2E.Message{ 783 | AudioMessage: &waE2E.AudioMessage{ 784 | URL: proto.String(audioUploaded.URL), 785 | DirectPath: proto.String(audioUploaded.DirectPath), 786 | Mimetype: proto.String(audioType), 787 | FileLength: proto.Uint64(audioUploaded.FileLength), 788 | FileSHA256: audioUploaded.FileSHA256, 789 | FileEncSHA256: audioUploaded.FileEncSHA256, 790 | MediaKey: audioUploaded.MediaKey, 791 | }, 792 | } 793 | 794 | // Send WhatsApp Message Proto 795 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgContent, msgExtra) 796 | if err != nil { 797 | return "", err 798 | } 799 | 800 | return msgExtra.ID, nil 801 | } 802 | 803 | // Return Error WhatsApp Client is not Valid 804 | return "", errors.New("WhatsApp Client is not Valid") 805 | } 806 | 807 | func WhatsAppSendVideo(ctx context.Context, jid string, rjid string, videoBytes []byte, videoType string, videoCaption string, isViewOnce bool) (string, error) { 808 | if WhatsAppClient[jid] != nil { 809 | var err error 810 | 811 | // Make Sure WhatsApp Client is OK 812 | err = WhatsAppIsClientOK(jid) 813 | if err != nil { 814 | return "", err 815 | } 816 | 817 | // Make Sure WhatsApp ID is Registered 818 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 819 | if err != nil { 820 | return "", err 821 | } 822 | 823 | // Set Chat Presence 824 | WhatsAppPresence(jid, true) 825 | WhatsAppComposeStatus(jid, remoteJID, true, false) 826 | defer func() { 827 | WhatsAppComposeStatus(jid, remoteJID, false, false) 828 | WhatsAppPresence(jid, false) 829 | }() 830 | 831 | // Upload Video to WhatsApp Storage Server 832 | videoUploaded, err := WhatsAppClient[jid].Upload(ctx, videoBytes, whatsmeow.MediaVideo) 833 | if err != nil { 834 | return "", errors.New("Error While Uploading Media to WhatsApp Server") 835 | } 836 | 837 | // Compose WhatsApp Proto 838 | msgExtra := whatsmeow.SendRequestExtra{ 839 | ID: WhatsAppClient[jid].GenerateMessageID(), 840 | } 841 | msgContent := &waE2E.Message{ 842 | VideoMessage: &waE2E.VideoMessage{ 843 | URL: proto.String(videoUploaded.URL), 844 | DirectPath: proto.String(videoUploaded.DirectPath), 845 | Mimetype: proto.String(videoType), 846 | Caption: proto.String(videoCaption), 847 | FileLength: proto.Uint64(videoUploaded.FileLength), 848 | FileSHA256: videoUploaded.FileSHA256, 849 | FileEncSHA256: videoUploaded.FileEncSHA256, 850 | MediaKey: videoUploaded.MediaKey, 851 | ViewOnce: proto.Bool(isViewOnce), 852 | }, 853 | } 854 | 855 | // Send WhatsApp Message Proto 856 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgContent, msgExtra) 857 | if err != nil { 858 | return "", err 859 | } 860 | 861 | return msgExtra.ID, nil 862 | } 863 | 864 | // Return Error WhatsApp Client is not Valid 865 | return "", errors.New("WhatsApp Client is not Valid") 866 | } 867 | 868 | func WhatsAppSendContact(ctx context.Context, jid string, rjid string, contactName string, contactNumber string) (string, error) { 869 | if WhatsAppClient[jid] != nil { 870 | var err error 871 | 872 | // Make Sure WhatsApp Client is OK 873 | err = WhatsAppIsClientOK(jid) 874 | if err != nil { 875 | return "", err 876 | } 877 | 878 | // Make Sure WhatsApp ID is Registered 879 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 880 | if err != nil { 881 | return "", err 882 | } 883 | 884 | // Set Chat Presence 885 | WhatsAppPresence(jid, true) 886 | WhatsAppComposeStatus(jid, remoteJID, true, false) 887 | defer func() { 888 | WhatsAppComposeStatus(jid, remoteJID, false, false) 889 | WhatsAppPresence(jid, false) 890 | }() 891 | 892 | // Compose WhatsApp Proto 893 | msgExtra := whatsmeow.SendRequestExtra{ 894 | ID: WhatsAppClient[jid].GenerateMessageID(), 895 | } 896 | msgVCard := fmt.Sprintf("BEGIN:VCARD\nVERSION:3.0\nN:;%v;;;\nFN:%v\nTEL;type=CELL;waid=%v:+%v\nEND:VCARD", 897 | contactName, contactName, contactNumber, contactNumber) 898 | msgContent := &waE2E.Message{ 899 | ContactMessage: &waE2E.ContactMessage{ 900 | DisplayName: proto.String(contactName), 901 | Vcard: proto.String(msgVCard), 902 | }, 903 | } 904 | 905 | // Send WhatsApp Message Proto 906 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgContent, msgExtra) 907 | if err != nil { 908 | return "", err 909 | } 910 | 911 | return msgExtra.ID, nil 912 | } 913 | 914 | // Return Error WhatsApp Client is not Valid 915 | return "", errors.New("WhatsApp Client is not Valid") 916 | } 917 | 918 | func WhatsAppSendLink(ctx context.Context, jid string, rjid string, linkCaption string, linkURL string) (string, error) { 919 | if WhatsAppClient[jid] != nil { 920 | var err error 921 | var urlTitle, urlDescription string 922 | 923 | // Make Sure WhatsApp Client is OK 924 | err = WhatsAppIsClientOK(jid) 925 | if err != nil { 926 | return "", err 927 | } 928 | 929 | // Make Sure WhatsApp ID is Registered 930 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 931 | if err != nil { 932 | return "", err 933 | } 934 | 935 | // Set Chat Presence 936 | WhatsAppPresence(jid, true) 937 | WhatsAppComposeStatus(jid, remoteJID, true, false) 938 | defer func() { 939 | WhatsAppComposeStatus(jid, remoteJID, false, false) 940 | WhatsAppPresence(jid, false) 941 | }() 942 | 943 | // Get URL Metadata 944 | urlResponse, err := http.Get(linkURL) 945 | if err != nil { 946 | return "", err 947 | } 948 | defer urlResponse.Body.Close() 949 | 950 | if urlResponse.StatusCode != 200 { 951 | return "", errors.New("Error While Fetching URL Metadata!") 952 | } 953 | 954 | // Query URL Metadata 955 | docData, err := goquery.NewDocumentFromReader(urlResponse.Body) 956 | if err != nil { 957 | return "", err 958 | } 959 | 960 | docData.Find("title").Each(func(index int, element *goquery.Selection) { 961 | urlTitle = element.Text() 962 | }) 963 | 964 | docData.Find("meta[name='description']").Each(func(index int, element *goquery.Selection) { 965 | urlDescription, _ = element.Attr("content") 966 | }) 967 | 968 | // Compose WhatsApp Proto 969 | msgExtra := whatsmeow.SendRequestExtra{ 970 | ID: WhatsAppClient[jid].GenerateMessageID(), 971 | } 972 | msgText := linkURL 973 | 974 | if len(strings.TrimSpace(linkCaption)) > 0 { 975 | msgText = fmt.Sprintf("%s\n%s", linkCaption, linkURL) 976 | } 977 | 978 | msgContent := &waE2E.Message{ 979 | ExtendedTextMessage: &waE2E.ExtendedTextMessage{ 980 | Text: proto.String(msgText), 981 | Title: proto.String(urlTitle), 982 | MatchedText: proto.String(linkURL), 983 | CanonicalURL: proto.String(linkURL), 984 | Description: proto.String(urlDescription), 985 | }, 986 | } 987 | 988 | // Send WhatsApp Message Proto 989 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgContent, msgExtra) 990 | if err != nil { 991 | return "", err 992 | } 993 | 994 | return msgExtra.ID, nil 995 | } 996 | 997 | // Return Error WhatsApp Client is not Valid 998 | return "", errors.New("WhatsApp Client is not Valid") 999 | } 1000 | 1001 | func WhatsAppSendSticker(ctx context.Context, jid string, rjid string, stickerBytes []byte) (string, error) { 1002 | if WhatsAppClient[jid] != nil { 1003 | var err error 1004 | 1005 | // Make Sure WhatsApp Client is OK 1006 | err = WhatsAppIsClientOK(jid) 1007 | if err != nil { 1008 | return "", err 1009 | } 1010 | 1011 | // Make Sure WhatsApp ID is Registered 1012 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 1013 | if err != nil { 1014 | return "", err 1015 | } 1016 | 1017 | // Set Chat Presence 1018 | WhatsAppPresence(jid, true) 1019 | WhatsAppComposeStatus(jid, remoteJID, true, false) 1020 | defer func() { 1021 | WhatsAppComposeStatus(jid, remoteJID, false, false) 1022 | WhatsAppPresence(jid, false) 1023 | }() 1024 | 1025 | stickerConvDecode, err := imgconv.Decode(bytes.NewReader(stickerBytes)) 1026 | if err != nil { 1027 | return "", errors.New("Error While Decoding Convert Sticker Stream") 1028 | } 1029 | 1030 | stickerConvResize := imgconv.Resize(stickerConvDecode, &imgconv.ResizeOption{Width: 512, Height: 512}) 1031 | stickerConvEncode := new(bytes.Buffer) 1032 | 1033 | err = webp.Encode(stickerConvEncode, stickerConvResize) 1034 | if err != nil { 1035 | return "", errors.New("Error While Encoding Convert Sticker Stream") 1036 | } 1037 | 1038 | stickerBytes = stickerConvEncode.Bytes() 1039 | 1040 | // Upload Image to WhatsApp Storage Server 1041 | stickerUploaded, err := WhatsAppClient[jid].Upload(ctx, stickerBytes, whatsmeow.MediaImage) 1042 | if err != nil { 1043 | return "", errors.New("Error While Uploading Media to WhatsApp Server") 1044 | } 1045 | 1046 | // Compose WhatsApp Proto 1047 | msgExtra := whatsmeow.SendRequestExtra{ 1048 | ID: WhatsAppClient[jid].GenerateMessageID(), 1049 | } 1050 | msgContent := &waE2E.Message{ 1051 | StickerMessage: &waE2E.StickerMessage{ 1052 | URL: proto.String(stickerUploaded.URL), 1053 | DirectPath: proto.String(stickerUploaded.DirectPath), 1054 | Mimetype: proto.String("image/webp"), 1055 | FileLength: proto.Uint64(stickerUploaded.FileLength), 1056 | FileSHA256: stickerUploaded.FileSHA256, 1057 | FileEncSHA256: stickerUploaded.FileEncSHA256, 1058 | MediaKey: stickerUploaded.MediaKey, 1059 | }, 1060 | } 1061 | 1062 | // Send WhatsApp Message Proto 1063 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgContent, msgExtra) 1064 | if err != nil { 1065 | return "", err 1066 | } 1067 | 1068 | return msgExtra.ID, nil 1069 | } 1070 | 1071 | // Return Error WhatsApp Client is not Valid 1072 | return "", errors.New("WhatsApp Client is not Valid") 1073 | } 1074 | 1075 | func WhatsAppSendPoll(ctx context.Context, jid string, rjid string, question string, options []string, isMultiAnswer bool) (string, error) { 1076 | if WhatsAppClient[jid] != nil { 1077 | var err error 1078 | 1079 | // Make Sure WhatsApp Client is OK 1080 | err = WhatsAppIsClientOK(jid) 1081 | if err != nil { 1082 | return "", err 1083 | } 1084 | 1085 | // Make Sure WhatsApp ID is Registered 1086 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 1087 | if err != nil { 1088 | return "", err 1089 | } 1090 | 1091 | // Set Chat Presence 1092 | WhatsAppPresence(jid, true) 1093 | WhatsAppComposeStatus(jid, remoteJID, true, false) 1094 | defer func() { 1095 | WhatsAppComposeStatus(jid, remoteJID, false, false) 1096 | WhatsAppPresence(jid, false) 1097 | }() 1098 | 1099 | // Check Options Must Be Equal or Greater Than 2 1100 | if len(options) < 2 { 1101 | return "", errors.New("WhatsApp Poll Options / Choices Must Be Equal or Greater Than 2") 1102 | } 1103 | 1104 | // Check if Poll Allow Multiple Answer 1105 | pollAnswerMax := 1 1106 | if isMultiAnswer { 1107 | pollAnswerMax = len(options) 1108 | } 1109 | 1110 | // Compose WhatsApp Proto 1111 | msgExtra := whatsmeow.SendRequestExtra{ 1112 | ID: WhatsAppClient[jid].GenerateMessageID(), 1113 | } 1114 | 1115 | // Send WhatsApp Message Proto 1116 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, WhatsAppClient[jid].BuildPollCreation(question, options, pollAnswerMax), msgExtra) 1117 | if err != nil { 1118 | return "", err 1119 | } 1120 | 1121 | return msgExtra.ID, nil 1122 | } 1123 | 1124 | // Return Error WhatsApp Client is not Valid 1125 | return "", errors.New("WhatsApp Client is not Valid") 1126 | } 1127 | 1128 | func WhatsAppMessageEdit(ctx context.Context, jid string, rjid string, msgid string, message string) (string, error) { 1129 | if WhatsAppClient[jid] != nil { 1130 | var err error 1131 | 1132 | // Make Sure WhatsApp Client is OK 1133 | err = WhatsAppIsClientOK(jid) 1134 | if err != nil { 1135 | return "", err 1136 | } 1137 | 1138 | // Make Sure WhatsApp ID is Registered 1139 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 1140 | if err != nil { 1141 | return "", err 1142 | } 1143 | 1144 | // Set Chat Presence 1145 | WhatsAppPresence(jid, true) 1146 | WhatsAppComposeStatus(jid, remoteJID, true, false) 1147 | defer func() { 1148 | WhatsAppComposeStatus(jid, remoteJID, false, false) 1149 | WhatsAppPresence(jid, false) 1150 | }() 1151 | 1152 | // Compose WhatsApp Proto 1153 | msgContent := &waE2E.Message{ 1154 | Conversation: proto.String(message), 1155 | } 1156 | 1157 | // Send WhatsApp Message Proto in Edit Mode 1158 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, WhatsAppClient[jid].BuildEdit(remoteJID, msgid, msgContent)) 1159 | if err != nil { 1160 | return "", err 1161 | } 1162 | 1163 | return msgid, nil 1164 | } 1165 | 1166 | // Return Error WhatsApp Client is not Valid 1167 | return "", errors.New("WhatsApp Client is not Valid") 1168 | } 1169 | 1170 | func WhatsAppMessageReact(ctx context.Context, jid string, rjid string, msgid string, emoji string) (string, error) { 1171 | if WhatsAppClient[jid] != nil { 1172 | var err error 1173 | 1174 | // Make Sure WhatsApp Client is OK 1175 | err = WhatsAppIsClientOK(jid) 1176 | if err != nil { 1177 | return "", err 1178 | } 1179 | 1180 | // Make Sure WhatsApp ID is Registered 1181 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 1182 | if err != nil { 1183 | return "", err 1184 | } 1185 | 1186 | // Set Chat Presence 1187 | WhatsAppPresence(jid, true) 1188 | WhatsAppComposeStatus(jid, remoteJID, true, false) 1189 | defer func() { 1190 | WhatsAppComposeStatus(jid, remoteJID, false, false) 1191 | WhatsAppPresence(jid, false) 1192 | }() 1193 | 1194 | // Check Emoji Must Be Contain Only 1 Emoji Character 1195 | if !gomoji.ContainsEmoji(emoji) && uniseg.GraphemeClusterCount(emoji) != 1 { 1196 | return "", errors.New("WhatsApp Message React Emoji Must Be Contain Only 1 Emoji Character") 1197 | } 1198 | 1199 | // Compose WhatsApp Proto 1200 | msgReact := &waE2E.Message{ 1201 | ReactionMessage: &waE2E.ReactionMessage{ 1202 | Key: &waCommon.MessageKey{ 1203 | FromMe: proto.Bool(true), 1204 | ID: proto.String(msgid), 1205 | RemoteJID: proto.String(remoteJID.String()), 1206 | }, 1207 | Text: proto.String(emoji), 1208 | SenderTimestampMS: proto.Int64(time.Now().UnixMilli()), 1209 | }, 1210 | } 1211 | 1212 | // Send WhatsApp Message Proto 1213 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, msgReact) 1214 | if err != nil { 1215 | return "", err 1216 | } 1217 | 1218 | return msgid, nil 1219 | } 1220 | 1221 | // Return Error WhatsApp Client is not Valid 1222 | return "", errors.New("WhatsApp Client is not Valid") 1223 | 1224 | } 1225 | 1226 | func WhatsAppMessageDelete(ctx context.Context, jid string, rjid string, msgid string) error { 1227 | if WhatsAppClient[jid] != nil { 1228 | var err error 1229 | 1230 | // Make Sure WhatsApp Client is OK 1231 | err = WhatsAppIsClientOK(jid) 1232 | if err != nil { 1233 | return err 1234 | } 1235 | 1236 | // Make Sure WhatsApp ID is Registered 1237 | remoteJID, err := WhatsAppCheckJID(jid, rjid) 1238 | if err != nil { 1239 | return err 1240 | } 1241 | 1242 | // Set Chat Presence 1243 | WhatsAppPresence(jid, true) 1244 | WhatsAppComposeStatus(jid, remoteJID, true, false) 1245 | defer func() { 1246 | WhatsAppComposeStatus(jid, remoteJID, false, false) 1247 | WhatsAppPresence(jid, false) 1248 | }() 1249 | 1250 | // Send WhatsApp Message Proto in Revoke Mode 1251 | _, err = WhatsAppClient[jid].SendMessage(ctx, remoteJID, WhatsAppClient[jid].BuildRevoke(remoteJID, types.EmptyJID, msgid)) 1252 | if err != nil { 1253 | return err 1254 | } 1255 | 1256 | return nil 1257 | } 1258 | 1259 | // Return Error WhatsApp Client is not Valid 1260 | return errors.New("WhatsApp Client is not Valid") 1261 | } 1262 | 1263 | func WhatsAppGroupGet(jid string) ([]types.GroupInfo, error) { 1264 | if WhatsAppClient[jid] != nil { 1265 | var err error 1266 | 1267 | // Make Sure WhatsApp Client is OK 1268 | err = WhatsAppIsClientOK(jid) 1269 | if err != nil { 1270 | return nil, err 1271 | } 1272 | 1273 | // Get Joined Group List 1274 | groups, err := WhatsAppClient[jid].GetJoinedGroups() 1275 | if err != nil { 1276 | return nil, err 1277 | } 1278 | 1279 | // Put Group Information in List 1280 | var gids []types.GroupInfo 1281 | for _, group := range groups { 1282 | gids = append(gids, *group) 1283 | } 1284 | 1285 | // Return Group Information List 1286 | return gids, nil 1287 | } 1288 | 1289 | // Return Error WhatsApp Client is not Valid 1290 | return nil, errors.New("WhatsApp Client is not Valid") 1291 | } 1292 | 1293 | func WhatsAppGroupJoin(jid string, link string) (string, error) { 1294 | if WhatsAppClient[jid] != nil { 1295 | var err error 1296 | 1297 | // Make Sure WhatsApp Client is OK 1298 | err = WhatsAppIsClientOK(jid) 1299 | if err != nil { 1300 | return "", err 1301 | } 1302 | 1303 | // Join Group By Invitation Link 1304 | gid, err := WhatsAppClient[jid].JoinGroupWithLink(link) 1305 | if err != nil { 1306 | return "", err 1307 | } 1308 | 1309 | // Return Joined Group ID 1310 | return gid.String(), nil 1311 | } 1312 | 1313 | // Return Error WhatsApp Client is not Valid 1314 | return "", errors.New("WhatsApp Client is not Valid") 1315 | } 1316 | 1317 | func WhatsAppGroupLeave(jid string, gjid string) error { 1318 | if WhatsAppClient[jid] != nil { 1319 | var err error 1320 | 1321 | // Make Sure WhatsApp Client is OK 1322 | err = WhatsAppIsClientOK(jid) 1323 | if err != nil { 1324 | return err 1325 | } 1326 | 1327 | // Make Sure WhatsApp ID is Registered 1328 | groupJID, err := WhatsAppCheckJID(jid, gjid) 1329 | if err != nil { 1330 | return err 1331 | } 1332 | 1333 | // Make Sure WhatsApp ID is Group Server 1334 | if groupJID.Server != types.GroupServer { 1335 | return errors.New("WhatsApp Group ID is Not Group Server") 1336 | } 1337 | 1338 | // Leave Group By Group ID 1339 | return WhatsAppClient[jid].LeaveGroup(groupJID) 1340 | } 1341 | 1342 | // Return Error WhatsApp Client is not Valid 1343 | return errors.New("WhatsApp Client is not Valid") 1344 | } 1345 | --------------------------------------------------------------------------------