Browse Source

construct base function for whatsapp multidevice

pull/4/head
Dimas Restu H 4 years ago
parent
commit
cbe0b4b1eb
  1. 1
      .dockerignore
  2. 18
      .env.example
  3. 1
      .gitignore
  4. 2
      Dockerfile
  5. 4
      cmd/main/main.go
  6. 0
      dbs/.gitkeep
  7. 25
      go.mod
  8. 33
      go.sum
  9. 50
      internal/auth/auth.go
  10. 4
      internal/auth/types/data.go
  11. 5
      internal/auth/types/request.go
  12. 5
      internal/auth/types/response.go
  13. 45
      internal/index/index.go
  14. 5
      internal/index/model/request.go
  15. 5
      internal/index/model/response.go
  16. 34
      internal/route.go
  17. 20
      internal/whatsapp/model/request.go
  18. 20
      internal/whatsapp/types/request.go
  19. 6
      internal/whatsapp/types/response.go
  20. 92
      internal/whatsapp/whatsapp.go
  21. 24
      pkg/auth/auth.go
  22. 5
      pkg/router/response.go
  23. 4
      pkg/router/router.go
  24. 175
      pkg/whatsapp/whatsapp.go

1
.dockerignore

@ -1,5 +1,6 @@
dist
vendor
dbs/*
.env
**/.DS_Store
**/.gitkeep

18
.env.example

@ -1 +1,17 @@
API_BASE_URL=/api/v1/whatsapp
# -----------------------------------
# HTTP Server Configuration
# -----------------------------------
# HTTP_BASE_URL=/
# HTTP_CORS_ORIGIN=*
# HTTP_BODY_LIMIT_SIZE=8m
# HTTP_GZIP_LEVEL=1
# -----------------------------------
# Authentication Configuration
# -----------------------------------
# AUTH_BASIC_USERNAME=admin
# AUTH_BASIC_PASSWORD=password
# AUTH_JWT_SECRET=secret
# AUTH_JWT_EXPIRED_HOUR=24

1
.gitignore

@ -1,5 +1,6 @@
dist
vendor
dbs/*
.env
*.DS_Store
!*.gitkeep

2
Dockerfile

@ -1,6 +1,6 @@
# Builder Image
# ---------------------------------------------------
FROM dimaskiddo/alpine:go-1.15 AS go-builder
FROM dimaskiddo/alpine:go-1.17 AS go-builder
WORKDIR /usr/src/app

4
cmd/main/main.go

@ -42,8 +42,8 @@ func main() {
// Router CORS
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{router.CORSOrigin},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization, echo.HeaderXRequestedWith},
AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.PATCH, echo.DELETE, echo.OPTIONS},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.PATCH, echo.DELETE},
}))
// Router Security

0
dbs/.gitkeep

25
go.mod

@ -1,11 +1,34 @@
module github.com/dimaskiddo/go-whatsapp-multidevice-rest
go 1.15
go 1.17
require (
github.com/go-playground/validator/v10 v10.6.1
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/joho/godotenv v1.3.0
github.com/labstack/echo/v4 v4.7.2
github.com/mattn/go-sqlite3 v1.14.11
github.com/sirupsen/logrus v1.8.1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
go.mau.fi/whatsmeow v0.0.0-20220416192948-8b34d886d543
)
require (
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
go.mau.fi/libsignal v0.0.0-20220315232917-871a40435d3b // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)

33
go.sum

@ -1,3 +1,5 @@
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@ -9,6 +11,10 @@ github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0
github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
@ -21,9 +27,13 @@ github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHR
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ=
github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -32,34 +42,35 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
go.mau.fi/libsignal v0.0.0-20220315232917-871a40435d3b h1:BGm0ceth6OtoXgBksEyeruiCL2ngZCwt86DUmUI6TmQ=
go.mau.fi/libsignal v0.0.0-20220315232917-871a40435d3b/go.mod h1:XYWsswZT1LfDmguWKYDuj+OugtdGX6CP3iwTtOcAGt4=
go.mau.fi/whatsmeow v0.0.0-20220416192948-8b34d886d543 h1:bSRqMKTczahOls67gsx/CxY0GiaGkqs0RQoyWhIb+Dg=
go.mau.fi/whatsmeow v0.0.0-20220416192948-8b34d886d543/go.mod h1:P7OA9XyJ/0dWHhUPKNESpC1wVOErnhY4pLEaMC1a8yg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

50
internal/auth/auth.go

@ -0,0 +1,50 @@
package auth
import (
"encoding/json"
"time"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
)
// Auth
func Auth(c echo.Context) error {
var reqAuthBasicInfo typAuth.RequestAuthBasicInfo
var resAuthJWTData typAuth.ResponseAuthJWTData
// Parse Basic Auth Information from Rewrited Body Request
// By Basic Auth Middleware
_ = json.NewDecoder(c.Request().Body).Decode(&reqAuthBasicInfo)
// Create JWT Claims
jwtClaims := &typAuth.AuthJWTClaims{
typAuth.AuthJWTClaimsPayload{
JID: reqAuthBasicInfo.Username,
},
jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(time.Hour * time.Duration(auth.AuthJWTExpiredHour)).Unix(),
},
}
// Create JWT Token
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims)
// Generate Encoded JWT Token
jwtTokenEncoded, err := jwtToken.SignedString([]byte(auth.AuthJWTSecret))
if err != nil {
return router.ResponseInternalError(c, "")
}
// Set Encoded JWT Token as Response Data
resAuthJWTData.Token = jwtTokenEncoded
// Return JWT Token in JSON Response
return router.ResponseSuccessWithData(c, "Successfully Authenticated", resAuthJWTData)
}

4
internal/index/auth/jwt.go → internal/auth/types/data.go

@ -1,4 +1,4 @@
package model
package types
import (
"github.com/golang-jwt/jwt"
@ -10,5 +10,5 @@ type AuthJWTClaims struct {
}
type AuthJWTClaimsPayload struct {
MSISDN string `json:"msisdn"`
JID string `json:"jid"`
}

5
internal/auth/types/request.go

@ -0,0 +1,5 @@
package types
type RequestAuthBasicInfo struct {
Username string
}

5
internal/auth/types/response.go

@ -0,0 +1,5 @@
package types
type ResponseAuthJWTData struct {
Token string `json:"token"`
}

45
internal/index/index.go

@ -1,57 +1,12 @@
package index
import (
"encoding/json"
"time"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
indexAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/auth"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/model"
)
// Index
func Index(c echo.Context) error {
return router.ResponseSuccess(c, "Go WhatsApp Multi-Device REST is running")
}
// Auth
func Auth(c echo.Context) error {
var reqAuthBasicInfo model.ReqAuthBasicInfo
var resAuthJWTData model.ResAuthJWTData
// Parse Basic Auth Information from Rewrited Body Request
// By Basic Auth Middleware
_ = json.NewDecoder(c.Request().Body).Decode(&reqAuthBasicInfo)
// Create JWT Claims
jwtClaims := &indexAuth.AuthJWTClaims{
indexAuth.AuthJWTClaimsPayload{
MSISDN: reqAuthBasicInfo.Username,
},
jwt.StandardClaims{
Issuer: "go-whatsapp-multidevice-rest",
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(time.Hour * time.Duration(auth.AuthJWTExpiredHour)).Unix(),
},
}
// Create JWT Token
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims)
// Generate Encoded JWT Token
jwtTokenEncoded, err := jwtToken.SignedString([]byte(auth.AuthJWTSecret))
if err != nil {
return router.ResponseInternalError(c, "")
}
// Set Encoded JWT Token as Response Data
resAuthJWTData.Token = jwtTokenEncoded
// Return JWT Token in JSON Response
return router.ResponseSuccessWithData(c, "", resAuthJWTData)
}

5
internal/index/model/request.go

@ -1,5 +0,0 @@
package model
type ReqAuthBasicInfo struct {
Username string
}

5
internal/index/model/response.go

@ -1,5 +0,0 @@
package model
type ResAuthJWTData struct {
Token string `json:"token"`
}

34
internal/route.go

@ -7,29 +7,37 @@ import (
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index"
indexAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/auth"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp"
ctlAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth"
typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types"
ctlIndex "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index"
ctlWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp"
)
func Routes(e *echo.Echo) {
// Route for Index
// ---------------------------------------------
e.GET(router.BaseURL, index.Index)
e.GET(router.BaseURL+"/auth", index.Auth, auth.BasicAuth())
e.GET(router.BaseURL, ctlIndex.Index)
// Route for Auth
// ---------------------------------------------
e.GET(router.BaseURL+"/auth", ctlAuth.Auth, auth.BasicAuth())
// Route for WhatsApp
// ---------------------------------------------
authJWTConfig := middleware.JWTConfig{
Claims: &indexAuth.AuthJWTClaims{},
Claims: &typAuth.AuthJWTClaims{},
SigningKey: []byte(auth.AuthJWTSecret),
}
e.POST(router.BaseURL+"/login", whatsapp.Login, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/text", whatsapp.SendText, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/location", whatsapp.SendLocation, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/document", whatsapp.SendDocument, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/audio", whatsapp.SendAudio, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/image", whatsapp.SendImage, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/video", whatsapp.SendVideo, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/login", ctlWhatsApp.Login, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/text", ctlWhatsApp.SendText, middleware.JWTWithConfig(authJWTConfig))
/*
e.POST(router.BaseURL+"/send/location", ctlWhatsApp.SendLocation, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/document", ctlWhatsApp.SendDocument, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/audio", ctlWhatsApp.SendAudio, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/image", ctlWhatsApp.SendImage, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/video", ctlWhatsApp.SendVideo, middleware.JWTWithConfig(authJWTConfig))
*/
e.POST(router.BaseURL+"/logout", ctlWhatsApp.Logout, middleware.JWTWithConfig(authJWTConfig))
}

20
internal/whatsapp/model/request.go

@ -1,20 +0,0 @@
package model
type ReqLogin struct {
Output string
}
type ReqSendMessage struct {
MSISDN string
Message string
QuotedID string
QuotedMessage string
}
type ReqSendLocation struct {
MSISDN string
Latitude float64
Longitude float64
QuotedID string
QuotedMessage string
}

20
internal/whatsapp/types/request.go

@ -0,0 +1,20 @@
package types
type RequestLogin struct {
Output string
}
type RequestSendMessage struct {
RJID string
Message string
// QuotedID string
// QuotedMessage string
}
type RequestSendLocation struct {
RJID string
Latitude float64
Longitude float64
// QuotedID string
// QuotedMessage string
}

6
internal/whatsapp/types/response.go

@ -0,0 +1,6 @@
package types
type ResponseLogin struct {
Timeout string `json:"timeout"`
QRCode string `json:"qrcode"`
}

92
internal/whatsapp/whatsapp.go

@ -5,41 +5,113 @@ import (
"github.com/labstack/echo/v4"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
pkgWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/whatsapp"
indexAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/auth"
typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types"
typWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp/types"
)
func getJWTPayload(c echo.Context) indexAuth.AuthJWTClaimsPayload {
func jwtPayload(c echo.Context) typAuth.AuthJWTClaimsPayload {
jwtToken := c.Get("user").(*jwt.Token)
jwtClaims := jwtToken.Claims.(*indexAuth.AuthJWTClaims)
jwtClaims := jwtToken.Claims.(*typAuth.AuthJWTClaims)
return jwtClaims.Data
}
func Login(c echo.Context) error {
return router.ResponseSuccess(c, "")
jid := jwtPayload(c).JID
var reqLogin typWhatsApp.RequestLogin
reqLogin.Output = c.FormValue("output")
if reqLogin.Output == "" {
reqLogin.Output = "html"
}
err := pkgWhatsApp.WhatAppConnect(jid)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
qrCodeImage, qrCodeTimeout, err := pkgWhatsApp.WhatsAppLogin(jid)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
var resLogin typWhatsApp.ResponseLogin
resLogin.QRCode = qrCodeImage
resLogin.Timeout = qrCodeTimeout
if reqLogin.Output == "html" {
htmlContent := `
<html>
<head>
<title>WhatsApp MultiDevice Login</title>
</head>
<body>
<img src="` + resLogin.QRCode + `" />
<p>
<b>QR Code Scan</b>
<br/>
Timeout in ` + resLogin.Timeout + `
</p>
</body>
</html>`
return router.ResponseSuccessWithHTML(c, htmlContent)
}
return router.ResponseSuccessWithData(c, "Successfully Generated QR Code", resLogin)
}
func Logout(c echo.Context) error {
jid := jwtPayload(c).JID
err := pkgWhatsApp.WhatsAppLogout(jid)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
return router.ResponseSuccess(c, "Successfully Logged Out")
}
func SendText(c echo.Context) error {
return router.ResponseSuccess(c, "")
jid := jwtPayload(c).JID
var reqSendMessage typWhatsApp.RequestSendMessage
reqSendMessage.RJID = c.FormValue("msisdn")
reqSendMessage.Message = c.FormValue("message")
err := pkgWhatsApp.WhatsAppSendText(jid, reqSendMessage.RJID, reqSendMessage.Message)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
return router.ResponseSuccess(c, "Successfully Send Text Message")
}
/*
TODO: Send Media
*/
/*
func SendLocation(c echo.Context) error {
return router.ResponseSuccess(c, "")
return router.ResponseSuccess(c, "Successfully Send Location Message")
}
func SendDocument(c echo.Context) error {
return router.ResponseSuccess(c, "")
return router.ResponseSuccess(c, "Successfully Send Document Message")
}
func SendImage(c echo.Context) error {
return router.ResponseSuccess(c, "")
return router.ResponseSuccess(c, "Successfully Send Image Message")
}
func SendAudio(c echo.Context) error {
return router.ResponseSuccess(c, "")
return router.ResponseSuccess(c, "Successfully Send Audio Message")
}
func SendVideo(c echo.Context) error {
return router.ResponseSuccess(c, "")
return router.ResponseSuccess(c, "Successfully Send Video Message")
}
*/

24
pkg/auth/auth.go

@ -11,25 +11,9 @@ var AuthJWTSecret string
var AuthJWTExpiredHour int
func init() {
var err error
AuthBasicUsername, _ = env.GetEnvString("AUTH_BASIC_USERNAME")
AuthBasicPassword, _ = env.GetEnvString("AUTH_BASIC_PASSWORD")
AuthBasicUsername, err = env.GetEnvString("AUTH_BASIC_USERNAME")
if err != nil {
AuthBasicUsername = "administrator"
}
AuthBasicPassword, err = env.GetEnvString("AUTH_BASIC_PASSWORD")
if err != nil {
AuthBasicPassword = "83e4060e-78e1-4fe5-9977-aeeccd46a2b8"
}
AuthJWTSecret, err = env.GetEnvString("AUTH_JWT_SECRET")
if err != nil {
AuthJWTSecret = "9e4eb4cf-be25-4a29-bba3-fefb5a30f6ab"
}
AuthJWTExpiredHour, err = env.GetEnvInt("AUTH_JWT_EXPIRED_HOUR")
if err != nil {
AuthJWTExpiredHour = 24
}
AuthJWTSecret, _ = env.GetEnvString("AUTH_JWT_SECRET")
AuthJWTExpiredHour, _ = env.GetEnvInt("AUTH_JWT_EXPIRED_HOUR")
}

5
pkg/router/response.go

@ -80,6 +80,11 @@ func ResponseSuccessWithData(c echo.Context, message string, data interface{}) e
return c.JSON(response.Code, response)
}
func ResponseSuccessWithHTML(c echo.Context, html string) error {
logSuccess(c, http.StatusOK, http.StatusText(http.StatusOK))
return c.HTML(http.StatusOK, html)
}
func ResponseCreated(c echo.Context, message string) error {
var response ResSuccess

4
pkg/router/router.go

@ -10,7 +10,7 @@ var GZipLevel int
func init() {
var err error
BaseURL, err = env.GetEnvString("API_BASE_URL")
BaseURL, err = env.GetEnvString("HTTP_BASE_URL")
if err != nil {
BaseURL = "/"
}
@ -27,6 +27,6 @@ func init() {
GZipLevel, err = env.GetEnvInt("HTTP_GZIP_LEVEL")
if err != nil {
GZipLevel = 3
GZipLevel = 1
}
}

175
pkg/whatsapp/whatsapp.go

@ -1 +1,176 @@
package whatsapp
import (
"context"
"encoding/base64"
"errors"
"fmt"
"os"
_ "github.com/mattn/go-sqlite3"
qrCode "github.com/skip2/go-qrcode"
"google.golang.org/protobuf/proto"
"go.mau.fi/whatsmeow"
waproto "go.mau.fi/whatsmeow/binary/proto"
"go.mau.fi/whatsmeow/store"
"go.mau.fi/whatsmeow/store/sqlstore"
"go.mau.fi/whatsmeow/types"
)
var WhatsAppClient = make(map[string]*whatsmeow.Client)
func WhatsAppInit(jid string) (*whatsmeow.Client, error) {
// Prepare SQLite Database File and Connection Address
dbFileName := "dbs/" + jid + ".db"
dbAddress := fmt.Sprintf("file:%s?_foreign_keys=on", dbFileName)
// Create and Connect to SQLite Database
datastore, err := sqlstore.New("sqlite3", dbAddress, nil)
if err != nil {
return nil, errors.New("Failed to Connect SQLite Database")
}
// Get First WhatsApp Device from SQLite Database
device, err := datastore.GetFirstDevice()
if err != nil {
return nil, errors.New("Failed to Load WhatsApp Device")
}
// Set Client Properties
store.CompanionProps.Os = proto.String("Go WhatsApp MultiDevice REST")
store.CompanionProps.PlatformType = waproto.CompanionProps_DESKTOP.Enum()
// Create New Client Connection
client := whatsmeow.NewClient(device, nil)
// Return Client Connection
return client, nil
}
func WhatAppConnect(jid string) error {
if WhatsAppClient[jid] == nil {
// Initialize New WhatsApp Client
client, err := WhatsAppInit(jid)
if err != nil {
return err
}
// Set Created WhatsApp Client to Map
WhatsAppClient[jid] = client
}
return nil
}
func WhatsAppGenerateQR(qrChan <-chan whatsmeow.QRChannelItem) (string, string) {
qrChanCode := make(chan string)
qrChanTimeout := make(chan string)
qrChanBase64 := make(chan string)
// Get QR Code Data and Timeout
go func() {
for evt := range qrChan {
if evt.Event == "code" {
qrChanCode <- evt.Code
qrChanTimeout <- evt.Timeout.String()
}
}
}()
// Generate QR Code Data to PNG Base64 Format
go func() {
select {
case tmp := <-qrChanCode:
png, _ := qrCode.Encode(tmp, qrCode.Medium, 256)
qrChanBase64 <- base64.StdEncoding.EncodeToString(png)
}
}()
// Return QR Code and Timeout Information
return <-qrChanBase64, <-qrChanTimeout
}
func WhatsAppLogin(jid string) (string, string, error) {
if WhatsAppClient[jid] != nil {
// Make Sure WebSocket Connection is Disconnected
WhatsAppClient[jid].Disconnect()
if WhatsAppClient[jid].Store.ID == nil {
// Device ID is not Exist
// Generate QR Code
qrChanGenerate, _ := WhatsAppClient[jid].GetQRChannel(context.Background())
// Connect WebSocket while Initialize QR Code Data to be Sent
err := WhatsAppClient[jid].Connect()
if err != nil {
return "", "", err
}
// Get Generated QR Code and Timeout Information
qrImage, qrTimeout := WhatsAppGenerateQR(qrChanGenerate)
// Return QR Code in Base64 Format and Timeout Information
return "data:image/png;base64," + qrImage, qrTimeout, nil
} else {
// Device ID is Exist
// Reconnect WebSocket
err := WhatsAppClient[jid].Connect()
if err != nil {
return "", "", err
}
}
}
// Return Error WhatsApp Client is Not Valid
return "", "", errors.New("WhatsApp Client is not Valid")
}
func WhatsAppLogout(jid string) error {
if WhatsAppClient[jid] != nil {
// Logout WhatsApp Client and Disconnect from WebSocket
err := WhatsAppClient[jid].Logout()
if err != nil {
return err
}
// Remove SQLite Database File
_ = os.Remove("dbs/" + jid + ".db")
// Free WhatsApp Client Map
WhatsAppClient[jid] = nil
delete(WhatsAppClient, jid)
return nil
}
// Return Error WhatsApp Client is Not Valid
return errors.New("WhatsApp Client is not Valid")
}
func WhatsAppCreateUserJID(jid string) types.JID {
return types.NewJID(jid, types.DefaultUserServer)
}
func WhatsAppCreateGroupJID(gjid string) types.JID {
return types.NewJID(gjid, types.GroupServer)
}
func WhatsAppSendText(jid string, rjid string, message string) error {
if WhatsAppClient[jid] != nil {
if WhatsAppClient[jid].IsConnected() && WhatsAppClient[jid].IsLoggedIn() {
_, err := WhatsAppClient[jid].SendMessage(WhatsAppCreateUserJID(rjid), "", &waproto.Message{
Conversation: proto.String(message),
})
if err != nil {
return err
}
return nil
}
}
// Return Error WhatsApp Client is Not Valid
return errors.New("WhatsApp Client is not Valid")
}
Loading…
Cancel
Save