Browse Source

feat: introduce domain (#28)

* feat: introduce domain

* fix: context parsing

* feat: add fetch all connected devices

* feat: move controller to transport layer rest

* feat: change struct service name

* feat: add middleware JID

* feat: remove unsed package
pull/31/head v3.9.0
Aldino Kemal 3 years ago
committed by GitHub
parent
commit
7b7b66818c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      src/cmd/root.go
  2. 8
      src/config/settings.go
  3. 58
      src/controllers/app_controller.go
  4. 12
      src/domains/app/app.go
  5. 6
      src/domains/app/devices.go
  6. 6
      src/domains/app/login.go
  7. 12
      src/domains/send/contact.go
  8. 13
      src/domains/send/file.go
  9. 16
      src/domains/send/image.go
  10. 18
      src/domains/send/send.go
  11. 11
      src/domains/send/text.go
  12. 16
      src/domains/send/video.go
  13. 48
      src/domains/user/account.go
  14. 12
      src/domains/user/user.go
  15. 2
      src/go.mod
  16. 6
      src/go.sum
  17. 69
      src/internal/rest/app_rest.go
  18. 66
      src/internal/rest/send_controller.go
  19. 41
      src/internal/rest/user_controller.go
  20. 21
      src/middleware/select_jid.go
  21. 145
      src/services/app_service.go
  22. 118
      src/services/app_service_impl.go
  23. 312
      src/services/send_service.go
  24. 310
      src/services/send_service_impl.go
  25. 115
      src/services/user_service.go
  26. 114
      src/services/user_service_impl.go
  27. 67
      src/structs/send_struct.go
  28. 48
      src/structs/user_struct.go
  29. 2
      src/utils/whatsapp.go
  30. 12
      src/validations/send_validation.go
  31. 6
      src/validations/send_validation_test.go
  32. 6
      src/validations/user_validation.go
  33. 4
      src/views/index.html

31
src/cmd/root.go

@ -4,7 +4,7 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config" "github.com/aldinokemal/go-whatsapp-web-multidevice/config"
"github.com/aldinokemal/go-whatsapp-web-multidevice/controllers"
"github.com/aldinokemal/go-whatsapp-web-multidevice/internal/rest"
"github.com/aldinokemal/go-whatsapp-web-multidevice/middleware" "github.com/aldinokemal/go-whatsapp-web-multidevice/middleware"
"github.com/aldinokemal/go-whatsapp-web-multidevice/services" "github.com/aldinokemal/go-whatsapp-web-multidevice/services"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
@ -63,6 +63,7 @@ func runRest(cmd *cobra.Command, args []string) {
}) })
app.Static("/statics", "./statics") app.Static("/statics", "./statics")
app.Use(middleware.Recovery()) app.Use(middleware.Recovery())
app.Use(middleware.SelectJid())
if config.AppDebug { if config.AppDebug {
app.Use(logger.New()) app.Use(logger.New())
} }
@ -71,19 +72,6 @@ func runRest(cmd *cobra.Command, args []string) {
AllowHeaders: "Origin, Content-Type, Accept", AllowHeaders: "Origin, Content-Type, Accept",
})) }))
db := utils.InitWaDB()
cli := utils.InitWaCLI(db)
// Service
appService := services.NewAppService(cli)
sendService := services.NewSendService(cli)
userService := services.NewUserService(cli)
// Controller
appController := controllers.NewAppController(appService)
sendController := controllers.NewSendController(sendService)
userController := controllers.NewUserController(userService)
if config.AppBasicAuthCredential != "" { if config.AppBasicAuthCredential != "" {
ba := strings.Split(config.AppBasicAuthCredential, ":") ba := strings.Split(config.AppBasicAuthCredential, ":")
if len(ba) != 2 { if len(ba) != 2 {
@ -97,9 +85,18 @@ func runRest(cmd *cobra.Command, args []string) {
})) }))
} }
appController.Route(app)
sendController.Route(app)
userController.Route(app)
db := utils.InitWaDB()
cli := utils.InitWaCLI(db)
// Service
appService := services.NewAppService(cli, db)
sendService := services.NewSendService(cli)
userService := services.NewUserService(cli)
// Rest
rest.InitRestApp(app, appService)
rest.InitRestSend(app, sendService)
rest.InitRestUser(app, userService)
app.Get("/", func(ctx *fiber.Ctx) error { app.Get("/", func(ctx *fiber.Ctx) error {
return ctx.Render("index", fiber.Map{ return ctx.Render("index", fiber.Map{

8
src/config/settings.go

@ -11,17 +11,19 @@ var (
AppDebug = false AppDebug = false
AppOs = fmt.Sprintf("AldinoKemal") AppOs = fmt.Sprintf("AldinoKemal")
AppPlatform = waProto.DeviceProps_PlatformType(1) AppPlatform = waProto.DeviceProps_PlatformType(1)
AppSelectedDeviceKey = "deviceID"
AppDefaultDevice = "default"
AppBasicAuthCredential string AppBasicAuthCredential string
PathQrCode = "statics/qrcode" PathQrCode = "statics/qrcode"
PathSendItems = "statics/senditems" PathSendItems = "statics/senditems"
PathStorages = "storages" PathStorages = "storages"
DBName = "hydrogenWaCli.db"
DBName = "whatsapp.db"
WhatsappLogLevel = "ERROR" WhatsappLogLevel = "ERROR"
WhatsappAutoReplyMessage string WhatsappAutoReplyMessage string
WhatsappAutoReplyWebhook string WhatsappAutoReplyWebhook string
WhatsappSettingMaxFileSize int64 = 30000000 // 10MB
WhatsappSettingMaxVideoSize int64 = 30000000 // 30MB
WhatsappSettingMaxFileSize int64 = 50000000 // 50MB
WhatsappSettingMaxVideoSize int64 = 100000000 // 100MB
) )

58
src/controllers/app_controller.go

@ -1,58 +0,0 @@
package controllers
import (
"fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/services"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/gofiber/fiber/v2"
)
type AppController struct {
Service services.AppService
}
func NewAppController(service services.AppService) AppController {
return AppController{Service: service}
}
func (controller *AppController) Route(app *fiber.App) {
app.Get("/app/login", controller.Login)
app.Get("/app/logout", controller.Logout)
app.Get("/app/reconnect", controller.Reconnect)
}
func (controller *AppController) Login(c *fiber.Ctx) error {
response, err := controller.Service.Login(c)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Code: 200,
Message: "Success",
Results: map[string]interface{}{
"qr_link": fmt.Sprintf("%s://%s/%s", c.Protocol(), c.Hostname(), response.ImagePath),
"qr_duration": response.Duration,
},
})
}
func (controller *AppController) Logout(c *fiber.Ctx) error {
err := controller.Service.Logout(c)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Code: 200,
Message: "Success logout",
Results: nil,
})
}
func (controller *AppController) Reconnect(c *fiber.Ctx) error {
err := controller.Service.Reconnect(c)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Code: 200,
Message: "Reconnect success",
Results: nil,
})
}

12
src/domains/app/app.go

@ -0,0 +1,12 @@
package app
import (
"context"
)
type IAppService interface {
Login(ctx context.Context) (response LoginResponse, err error)
Logout(ctx context.Context) (err error)
Reconnect(ctx context.Context) (err error)
FetchDevices(ctx context.Context) (response []FetchDevicesResponse, err error)
}

6
src/domains/app/devices.go

@ -0,0 +1,6 @@
package app
type FetchDevicesResponse struct {
Name string
Device string
}

6
src/structs/auth_struct.go → src/domains/app/login.go

@ -1,8 +1,6 @@
package structs
package app
import (
"time"
)
import "time"
type LoginResponse struct { type LoginResponse struct {
ImagePath string `json:"image_path"` ImagePath string `json:"image_path"`

12
src/domains/send/contact.go

@ -0,0 +1,12 @@
package send
type ContactRequest struct {
Phone string `json:"phone" form:"phone"`
ContactName string `json:"contact_name" form:"contact_name"`
ContactPhone string `json:"contact_phone" form:"contact_phone"`
Type Type `json:"type" form:"type"`
}
type ContactResponse struct {
Status string `json:"status"`
}

13
src/domains/send/file.go

@ -0,0 +1,13 @@
package send
import "mime/multipart"
type FileRequest struct {
Phone string `json:"phone" form:"phone"`
File *multipart.FileHeader `json:"file" form:"file"`
Type Type `json:"type" form:"type"`
}
type FileResponse struct {
Status string `json:"status"`
}

16
src/domains/send/image.go

@ -0,0 +1,16 @@
package send
import "mime/multipart"
type ImageRequest struct {
Phone string `json:"phone" form:"phone"`
Caption string `json:"caption" form:"caption"`
Image *multipart.FileHeader `json:"image" form:"image"`
ViewOnce bool `json:"view_once" form:"view_once"`
Type Type `json:"type" form:"type"`
Compress bool `json:"compress"`
}
type ImageResponse struct {
Status string `json:"status"`
}

18
src/domains/send/send.go

@ -0,0 +1,18 @@
package send
import (
"context"
)
type Type string
const TypeUser Type = "user"
const TypeGroup Type = "group"
type ISendService interface {
SendText(ctx context.Context, request MessageRequest) (response MessageResponse, err error)
SendImage(ctx context.Context, request ImageRequest) (response ImageResponse, err error)
SendFile(ctx context.Context, request FileRequest) (response FileResponse, err error)
SendVideo(ctx context.Context, request VideoRequest) (response VideoResponse, err error)
SendContact(ctx context.Context, request ContactRequest) (response ContactResponse, err error)
}

11
src/domains/send/text.go

@ -0,0 +1,11 @@
package send
type MessageRequest struct {
Phone string `json:"phone" form:"phone"`
Message string `json:"message" form:"message"`
Type Type `json:"type" form:"type"`
}
type MessageResponse struct {
Status string `json:"status"`
}

16
src/domains/send/video.go

@ -0,0 +1,16 @@
package send
import "mime/multipart"
type VideoRequest struct {
Phone string `json:"phone" form:"phone"`
Caption string `json:"caption" form:"caption"`
Video *multipart.FileHeader `json:"video" form:"video"`
Type Type `json:"type" form:"type"`
ViewOnce bool `json:"view_once" form:"view_once"`
Compress bool `json:"compress"`
}
type VideoResponse struct {
Status string `json:"status"`
}

48
src/domains/user/account.go

@ -0,0 +1,48 @@
package user
import "go.mau.fi/whatsmeow/types"
type InfoRequest struct {
Phone string `json:"phone" query:"phone"`
}
type InfoResponseDataDevice struct {
User string
Agent uint8
Device string
Server string
AD bool
}
type InfoResponseData struct {
VerifiedName string `json:"verified_name"`
Status string `json:"status"`
PictureID string `json:"picture_id"`
Devices []InfoResponseDataDevice `json:"devices"`
}
type InfoResponse struct {
Data []InfoResponseData `json:"data"`
}
type AvatarRequest struct {
Phone string `json:"phone" query:"phone"`
}
type AvatarResponse struct {
URL string `json:"url"`
ID string `json:"id"`
Type string `json:"type"`
}
type MyPrivacySettingResponse struct {
GroupAdd string `json:"group_add"`
LastSeen string `json:"last_seen"`
Status string `json:"status"`
Profile string `json:"profile"`
ReadReceipts string `json:"read_receipts"`
}
type MyListGroupsResponse struct {
Data []types.GroupInfo `json:"data"`
}

12
src/domains/user/user.go

@ -0,0 +1,12 @@
package user
import (
"context"
)
type IUserService interface {
Info(ctx context.Context, request InfoRequest) (response InfoResponse, err error)
Avatar(ctx context.Context, request AvatarRequest) (response AvatarResponse, err error)
MyListGroups(ctx context.Context) (response MyListGroupsResponse, err error)
MyPrivacySetting(ctx context.Context) (response MyPrivacySettingResponse, err error)
}

2
src/go.mod

@ -13,6 +13,7 @@ require (
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
github.com/valyala/fasthttp v1.41.0
go.mau.fi/whatsmeow v0.0.0-20221117205735-357841c4ff30 go.mau.fi/whatsmeow v0.0.0-20221117205735-357841c4ff30
google.golang.org/protobuf v1.28.1 google.golang.org/protobuf v1.28.1
) )
@ -33,7 +34,6 @@ require (
github.com/rivo/uniseg v0.4.2 // indirect github.com/rivo/uniseg v0.4.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.41.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf // indirect go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf // indirect
golang.org/x/crypto v0.1.0 // indirect golang.org/x/crypto v0.1.0 // indirect

6
src/go.sum

@ -140,13 +140,9 @@ github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PL
github.com/gobuffalo/here v0.6.7 h1:hpfhh+kt2y9JLDfhYUxxCRxQol540jsVfKUZzjlbp8o= github.com/gobuffalo/here v0.6.7 h1:hpfhh+kt2y9JLDfhYUxxCRxQol540jsVfKUZzjlbp8o=
github.com/gobuffalo/here v0.6.7/go.mod h1:vuCfanjqckTuRlqAitJz6QC4ABNnS27wLb816UhsPcc= github.com/gobuffalo/here v0.6.7/go.mod h1:vuCfanjqckTuRlqAitJz6QC4ABNnS27wLb816UhsPcc=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.37.1/go.mod h1:j3UslgQeJQP3mNhBxHnLLE8TPqA1Fd/lrl4gD25rRUY=
github.com/gofiber/fiber/v2 v2.39.0 h1:uhWpYQ6EHN8J7FOPYbI2hrdBD/KNZBC5CjbuOd4QUt4=
github.com/gofiber/fiber/v2 v2.39.0/go.mod h1:Cmuu+elPYGqlvQvdKyjtYsjGMi69PDp8a1AY2I5B2gM= github.com/gofiber/fiber/v2 v2.39.0/go.mod h1:Cmuu+elPYGqlvQvdKyjtYsjGMi69PDp8a1AY2I5B2gM=
github.com/gofiber/fiber/v2 v2.40.0 h1:fdU7w5hT6PLL7jiWIhtQ+S/k5WEFYoUZidptlPu8GBo= github.com/gofiber/fiber/v2 v2.40.0 h1:fdU7w5hT6PLL7jiWIhtQ+S/k5WEFYoUZidptlPu8GBo=
github.com/gofiber/fiber/v2 v2.40.0/go.mod h1:Gko04sLksnHbzLSRBFWPFdzM9Ws9pRxvvIaohJK1dsk= github.com/gofiber/fiber/v2 v2.40.0/go.mod h1:Gko04sLksnHbzLSRBFWPFdzM9Ws9pRxvvIaohJK1dsk=
github.com/gofiber/template v1.7.1 h1:QCRChZA6UrLROgMbzCMKm4a1yqM/5S8RTBKYWZ9GfL4=
github.com/gofiber/template v1.7.1/go.mod h1:l3ZOSp8yrMvROzqyh0QTCw7MHet/yLBzaRX+wsiw+gM=
github.com/gofiber/template v1.7.2 h1:sCHY5WcvmLtR7t8ihXCs5HRdYv4W9EzgBIMbUl4a5zw= github.com/gofiber/template v1.7.2 h1:sCHY5WcvmLtR7t8ihXCs5HRdYv4W9EzgBIMbUl4a5zw=
github.com/gofiber/template v1.7.2/go.mod h1:uk/mUKTXJMBMoETvo/5CA/QlTFERHMooOm0pJPGDGVA= github.com/gofiber/template v1.7.2/go.mod h1:uk/mUKTXJMBMoETvo/5CA/QlTFERHMooOm0pJPGDGVA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -408,8 +404,6 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf h1:mzPxXBgDPHKDHMVV1tIWh7lwCiRpzCsXC0gNRX+K07c= go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf h1:mzPxXBgDPHKDHMVV1tIWh7lwCiRpzCsXC0gNRX+K07c=
go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf/go.mod h1:XCjaU93vl71YNRPn059jMrK0xRDwVO5gKbxoPxow9mQ= go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf/go.mod h1:XCjaU93vl71YNRPn059jMrK0xRDwVO5gKbxoPxow9mQ=
go.mau.fi/whatsmeow v0.0.0-20221104090956-123ccfa04185 h1:TTya4FuY3E8s+Dl7Srzv9fKbhvJeaWEcpQqa/WadsKo=
go.mau.fi/whatsmeow v0.0.0-20221104090956-123ccfa04185/go.mod h1:elpwNbr2EwG1kadbswg+fKjlWffYUmOxqvx7Q8uX9YE=
go.mau.fi/whatsmeow v0.0.0-20221117205735-357841c4ff30 h1:AVHEkGJSI6NsdbxVs9YEGPFR68uru1Ke/hH72EqzGpk= go.mau.fi/whatsmeow v0.0.0-20221117205735-357841c4ff30 h1:AVHEkGJSI6NsdbxVs9YEGPFR68uru1Ke/hH72EqzGpk=
go.mau.fi/whatsmeow v0.0.0-20221117205735-357841c4ff30/go.mod h1:2yweL8nczvtlIxkrvCb0y8xiO13rveX9lJPambwYV/E= go.mau.fi/whatsmeow v0.0.0-20221117205735-357841c4ff30/go.mod h1:2yweL8nczvtlIxkrvCb0y8xiO13rveX9lJPambwYV/E=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=

69
src/internal/rest/app_rest.go

@ -0,0 +1,69 @@
package rest
import (
"fmt"
domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/gofiber/fiber/v2"
)
type App struct {
Service domainApp.IAppService
}
func InitRestApp(app *fiber.App, service domainApp.IAppService) App {
rest := App{Service: service}
app.Get("/app/login", rest.Login)
app.Get("/app/logout", rest.Logout)
app.Get("/app/reconnect", rest.Reconnect)
app.Get("/app/devices", rest.Devices)
return App{Service: service}
}
func (controller *App) Login(c *fiber.Ctx) error {
response, err := controller.Service.Login(c.UserContext())
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Code: 200,
Message: "Success",
Results: map[string]interface{}{
"qr_link": fmt.Sprintf("%s://%s/%s", c.Protocol(), c.Hostname(), response.ImagePath),
"qr_duration": response.Duration,
},
})
}
func (controller *App) Logout(c *fiber.Ctx) error {
err := controller.Service.Logout(c.UserContext())
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Code: 200,
Message: "Success logout",
Results: nil,
})
}
func (controller *App) Reconnect(c *fiber.Ctx) error {
err := controller.Service.Reconnect(c.UserContext())
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Code: 200,
Message: "Reconnect success",
Results: nil,
})
}
func (controller *App) Devices(c *fiber.Ctx) error {
devices, err := controller.Service.FetchDevices(c.UserContext())
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Code: 200,
Message: "Fetch device success",
Results: devices,
})
}

66
src/controllers/send_controller.go → src/internal/rest/send_controller.go

@ -1,44 +1,42 @@
package controllers
package rest
import ( import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/services"
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
type SendController struct {
Service services.SendService
type Send struct {
Service domainSend.ISendService
} }
func NewSendController(service services.SendService) SendController {
return SendController{Service: service}
}
func InitRestSend(app *fiber.App, service domainSend.ISendService) Send {
rest := Send{Service: service}
app.Post("/send/message", rest.SendText)
app.Post("/send/image", rest.SendImage)
app.Post("/send/file", rest.SendFile)
app.Post("/send/video", rest.SendVideo)
app.Post("/send/contact", rest.SendContact)
func (controller *SendController) Route(app *fiber.App) {
app.Post("/send/message", controller.SendText)
app.Post("/send/image", controller.SendImage)
app.Post("/send/file", controller.SendFile)
app.Post("/send/video", controller.SendVideo)
app.Post("/send/contact", controller.SendContact)
return rest
} }
func (controller *SendController) SendText(c *fiber.Ctx) error {
var request structs.SendMessageRequest
func (controller *Send) SendText(c *fiber.Ctx) error {
var request domainSend.MessageRequest
err := c.BodyParser(&request) err := c.BodyParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
// add validation send message // add validation send message
validations.ValidateSendMessage(request) validations.ValidateSendMessage(request)
if request.Type == structs.TypeGroup {
if request.Type == domainSend.TypeGroup {
request.Phone = request.Phone + "@g.us" request.Phone = request.Phone + "@g.us"
} else { } else {
request.Phone = request.Phone + "@s.whatsapp.net" request.Phone = request.Phone + "@s.whatsapp.net"
} }
response, err := controller.Service.SendText(c, request)
response, err := controller.Service.SendText(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
@ -48,8 +46,8 @@ func (controller *SendController) SendText(c *fiber.Ctx) error {
}) })
} }
func (controller *SendController) SendImage(c *fiber.Ctx) error {
var request structs.SendImageRequest
func (controller *Send) SendImage(c *fiber.Ctx) error {
var request domainSend.ImageRequest
request.Compress = true request.Compress = true
err := c.BodyParser(&request) err := c.BodyParser(&request)
@ -63,13 +61,13 @@ func (controller *SendController) SendImage(c *fiber.Ctx) error {
//add validation send image //add validation send image
validations.ValidateSendImage(request) validations.ValidateSendImage(request)
if request.Type == structs.TypeGroup {
if request.Type == domainSend.TypeGroup {
request.Phone = request.Phone + "@g.us" request.Phone = request.Phone + "@g.us"
} else { } else {
request.Phone = request.Phone + "@s.whatsapp.net" request.Phone = request.Phone + "@s.whatsapp.net"
} }
response, err := controller.Service.SendImage(c, request)
response, err := controller.Service.SendImage(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
@ -79,8 +77,8 @@ func (controller *SendController) SendImage(c *fiber.Ctx) error {
}) })
} }
func (controller *SendController) SendFile(c *fiber.Ctx) error {
var request structs.SendFileRequest
func (controller *Send) SendFile(c *fiber.Ctx) error {
var request domainSend.FileRequest
err := c.BodyParser(&request) err := c.BodyParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
@ -92,13 +90,13 @@ func (controller *SendController) SendFile(c *fiber.Ctx) error {
//add validation send image //add validation send image
validations.ValidateSendFile(request) validations.ValidateSendFile(request)
if request.Type == structs.TypeGroup {
if request.Type == domainSend.TypeGroup {
request.Phone = request.Phone + "@g.us" request.Phone = request.Phone + "@g.us"
} else { } else {
request.Phone = request.Phone + "@s.whatsapp.net" request.Phone = request.Phone + "@s.whatsapp.net"
} }
response, err := controller.Service.SendFile(c, request)
response, err := controller.Service.SendFile(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
@ -108,8 +106,8 @@ func (controller *SendController) SendFile(c *fiber.Ctx) error {
}) })
} }
func (controller *SendController) SendVideo(c *fiber.Ctx) error {
var request structs.SendVideoRequest
func (controller *Send) SendVideo(c *fiber.Ctx) error {
var request domainSend.VideoRequest
err := c.BodyParser(&request) err := c.BodyParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
@ -121,13 +119,13 @@ func (controller *SendController) SendVideo(c *fiber.Ctx) error {
//add validation send image //add validation send image
validations.ValidateSendVideo(request) validations.ValidateSendVideo(request)
if request.Type == structs.TypeGroup {
if request.Type == domainSend.TypeGroup {
request.Phone = request.Phone + "@g.us" request.Phone = request.Phone + "@g.us"
} else { } else {
request.Phone = request.Phone + "@s.whatsapp.net" request.Phone = request.Phone + "@s.whatsapp.net"
} }
response, err := controller.Service.SendVideo(c, request)
response, err := controller.Service.SendVideo(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
@ -137,21 +135,21 @@ func (controller *SendController) SendVideo(c *fiber.Ctx) error {
}) })
} }
func (controller SendController) SendContact(c *fiber.Ctx) error {
var request structs.SendContactRequest
func (controller *Send) SendContact(c *fiber.Ctx) error {
var request domainSend.ContactRequest
err := c.BodyParser(&request) err := c.BodyParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
// add validation send contect // add validation send contect
validations.ValidateSendContact(request) validations.ValidateSendContact(request)
if request.Type == structs.TypeGroup {
if request.Type == domainSend.TypeGroup {
request.Phone = request.Phone + "@g.us" request.Phone = request.Phone + "@g.us"
} else { } else {
request.Phone = request.Phone + "@s.whatsapp.net" request.Phone = request.Phone + "@s.whatsapp.net"
} }
response, err := controller.Service.SendContact(c, request)
response, err := controller.Service.SendContact(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{

41
src/controllers/user_controller.go → src/internal/rest/user_controller.go

@ -1,30 +1,35 @@
package controllers
package rest
import ( import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/services"
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
type UserController struct {
Service services.UserService
type User struct {
Service domainUser.IUserService
} }
func NewUserController(service services.UserService) UserController {
return UserController{Service: service}
func InitRestUser(app *fiber.App, service domainUser.IUserService) User {
rest := User{Service: service}
app.Get("/user/info", rest.UserInfo)
app.Get("/user/avatar", rest.UserAvatar)
app.Get("/user/my/privacy", rest.UserMyPrivacySetting)
app.Get("/user/my/groups", rest.UserMyListGroups)
return rest
} }
func (controller *UserController) Route(app *fiber.App) {
func (controller *User) Route(app *fiber.App) {
app.Get("/user/info", controller.UserInfo) app.Get("/user/info", controller.UserInfo)
app.Get("/user/avatar", controller.UserAvatar) app.Get("/user/avatar", controller.UserAvatar)
app.Get("/user/my/privacy", controller.UserMyPrivacySetting) app.Get("/user/my/privacy", controller.UserMyPrivacySetting)
app.Get("/user/my/groups", controller.UserMyListGroups) app.Get("/user/my/groups", controller.UserMyListGroups)
} }
func (controller *UserController) UserInfo(c *fiber.Ctx) error {
var request structs.UserInfoRequest
func (controller *User) UserInfo(c *fiber.Ctx) error {
var request domainUser.InfoRequest
err := c.QueryParser(&request) err := c.QueryParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
@ -32,7 +37,7 @@ func (controller *UserController) UserInfo(c *fiber.Ctx) error {
validations.ValidateUserInfo(request) validations.ValidateUserInfo(request)
request.Phone = request.Phone + "@s.whatsapp.net" request.Phone = request.Phone + "@s.whatsapp.net"
response, err := controller.Service.UserInfo(c, request)
response, err := controller.Service.Info(c.Context(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
@ -42,8 +47,8 @@ func (controller *UserController) UserInfo(c *fiber.Ctx) error {
}) })
} }
func (controller *UserController) UserAvatar(c *fiber.Ctx) error {
var request structs.UserAvatarRequest
func (controller *User) UserAvatar(c *fiber.Ctx) error {
var request domainUser.AvatarRequest
err := c.QueryParser(&request) err := c.QueryParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
@ -51,7 +56,7 @@ func (controller *UserController) UserAvatar(c *fiber.Ctx) error {
validations.ValidateUserAvatar(request) validations.ValidateUserAvatar(request)
request.Phone = request.Phone + "@s.whatsapp.net" request.Phone = request.Phone + "@s.whatsapp.net"
response, err := controller.Service.UserAvatar(c, request)
response, err := controller.Service.Avatar(c.Context(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
@ -61,8 +66,8 @@ func (controller *UserController) UserAvatar(c *fiber.Ctx) error {
}) })
} }
func (controller *UserController) UserMyPrivacySetting(c *fiber.Ctx) error {
response, err := controller.Service.UserMyPrivacySetting(c)
func (controller *User) UserMyPrivacySetting(c *fiber.Ctx) error {
response, err := controller.Service.MyPrivacySetting(c.UserContext())
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
@ -72,8 +77,8 @@ func (controller *UserController) UserMyPrivacySetting(c *fiber.Ctx) error {
}) })
} }
func (controller *UserController) UserMyListGroups(c *fiber.Ctx) error {
response, err := controller.Service.UserMyListGroups(c)
func (controller *User) UserMyListGroups(c *fiber.Ctx) error {
response, err := controller.Service.MyListGroups(c.UserContext())
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{

21
src/middleware/select_jid.go

@ -0,0 +1,21 @@
package middleware
import (
"context"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
"github.com/gofiber/fiber/v2"
)
func SelectJid() fiber.Handler {
return func(c *fiber.Ctx) error {
selectedJid := string(c.Request().Header.Peek("jid"))
if selectedJid == "" {
selectedJid = config.AppDefaultDevice
}
ctx := context.WithValue(c.Context(), config.AppSelectedDeviceKey, selectedJid)
c.SetUserContext(ctx)
return c.Next()
}
}

145
src/services/app_service.go

@ -1,12 +1,145 @@
package services package services
import ( import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
"github.com/gofiber/fiber/v2"
"context"
"errors"
"fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app"
fiberUtils "github.com/gofiber/fiber/v2/utils"
"github.com/skip2/go-qrcode"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/store/sqlstore"
"os"
"path/filepath"
"time"
) )
type AppService interface {
Login(c *fiber.Ctx) (response structs.LoginResponse, err error)
Logout(c *fiber.Ctx) (err error)
Reconnect(c *fiber.Ctx) (err error)
type serviceApp struct {
WaCli *whatsmeow.Client
db *sqlstore.Container
}
func NewAppService(waCli *whatsmeow.Client, db *sqlstore.Container) domainApp.IAppService {
return &serviceApp{
WaCli: waCli,
db: db,
}
}
func (service serviceApp) Login(_ context.Context) (response domainApp.LoginResponse, err error) {
if service.WaCli == nil {
return response, errors.New("wa cli nil cok")
}
// Disconnect for reconnecting
service.WaCli.Disconnect()
chImage := make(chan string)
ch, err := service.WaCli.GetQRChannel(context.Background())
if err != nil {
// This error means that we're already logged in, so ignore it.
if errors.Is(err, whatsmeow.ErrQRStoreContainsID) {
_ = service.WaCli.Connect() // just connect to websocket
if service.WaCli.IsLoggedIn() {
return response, errors.New("you already logged in :)")
}
return response, errors.New("your session have been saved, please wait to connect 2 second and refresh again")
} else {
return response, errors.New("Error when GetQRChannel:" + err.Error())
}
} else {
go func() {
for evt := range ch {
response.Code = evt.Code
response.Duration = evt.Timeout / time.Second / 2
if evt.Event == "code" {
qrPath := fmt.Sprintf("%s/scan-qr-%s.png", config.PathQrCode, fiberUtils.UUIDv4())
err = qrcode.WriteFile(evt.Code, qrcode.Medium, 512, qrPath)
if err != nil {
fmt.Println("error when write qrImage file", err.Error())
}
go func() {
time.Sleep(response.Duration * time.Second)
err := os.Remove(qrPath)
if err != nil {
fmt.Println("Failed to remove qrPath " + qrPath)
}
}()
chImage <- qrPath
} else {
fmt.Printf("QR channel result: %s", evt.Event)
}
}
}()
}
err = service.WaCli.Connect()
if err != nil {
return response, errors.New("Failed to connect bro " + err.Error())
}
response.ImagePath = <-chImage
return response, nil
}
func (service serviceApp) Logout(_ context.Context) (err error) {
// delete history
files, err := filepath.Glob("./history-*")
if err != nil {
return err
}
for _, f := range files {
err = os.Remove(f)
if err != nil {
return err
}
}
// delete qr images
qrImages, err := filepath.Glob("./statics/images/qrcode/scan-*")
if err != nil {
return err
}
for _, f := range qrImages {
err = os.Remove(f)
if err != nil {
return err
}
}
err = service.WaCli.Logout()
return
}
func (service serviceApp) Reconnect(_ context.Context) (err error) {
service.WaCli.Disconnect()
return service.WaCli.Connect()
}
func (service serviceApp) FetchDevices(ctx context.Context) (response []domainApp.FetchDevicesResponse, err error) {
if service.WaCli == nil {
return response, errors.New("wa cli nil cok")
}
devices, err := service.db.GetAllDevices()
if err != nil {
return nil, err
}
for _, device := range devices {
var d domainApp.FetchDevicesResponse
d.Device = device.ID.String()
if device.PushName != "" {
d.Name = device.PushName
} else {
d.Name = device.BusinessName
}
response = append(response, d)
}
return response, nil
} }

118
src/services/app_service_impl.go

@ -1,118 +0,0 @@
package services
import (
"context"
"errors"
"fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
"github.com/gofiber/fiber/v2"
fiberutils "github.com/gofiber/fiber/v2/utils"
"github.com/skip2/go-qrcode"
"go.mau.fi/whatsmeow"
"os"
"path/filepath"
"time"
)
type AppServiceImpl struct {
WaCli *whatsmeow.Client
}
func NewAppService(waCli *whatsmeow.Client) AppService {
return &AppServiceImpl{
WaCli: waCli,
}
}
func (service AppServiceImpl) Login(c *fiber.Ctx) (response structs.LoginResponse, err error) {
if service.WaCli == nil {
return response, errors.New("wa cli nil cok")
}
// Disconnect for reconnecting
service.WaCli.Disconnect()
chImage := make(chan string)
ch, err := service.WaCli.GetQRChannel(context.Background())
if err != nil {
// This error means that we're already logged in, so ignore it.
if errors.Is(err, whatsmeow.ErrQRStoreContainsID) {
_ = service.WaCli.Connect() // just connect to websocket
if service.WaCli.IsLoggedIn() {
return response, errors.New("you already logged in :)")
}
return response, errors.New("your session have been saved, please wait to connect 2 second and refresh again")
} else {
return response, errors.New("Error when GetQRChannel:" + err.Error())
}
} else {
go func() {
for evt := range ch {
response.Code = evt.Code
response.Duration = evt.Timeout / time.Second / 2
if evt.Event == "code" {
qrPath := fmt.Sprintf("%s/scan-qr-%s.png", config.PathQrCode, fiberutils.UUIDv4())
err = qrcode.WriteFile(evt.Code, qrcode.Medium, 512, qrPath)
if err != nil {
fmt.Println("error when write qrImage file", err.Error())
}
go func() {
time.Sleep(response.Duration * time.Second)
err := os.Remove(qrPath)
if err != nil {
fmt.Println("Failed to remove qrPath " + qrPath)
}
}()
chImage <- qrPath
} else {
fmt.Printf("QR channel result: %s", evt.Event)
}
}
}()
}
err = service.WaCli.Connect()
if err != nil {
return response, errors.New("Failed to connect bro " + err.Error())
}
response.ImagePath = <-chImage
return response, nil
}
func (service AppServiceImpl) Logout(c *fiber.Ctx) (err error) {
// delete history
files, err := filepath.Glob("./history-*")
if err != nil {
panic(err)
}
for _, f := range files {
err = os.Remove(f)
if err != nil {
return err
}
}
// delete qr images
qrImages, err := filepath.Glob("./statics/images/qrcode/scan-*")
if err != nil {
panic(err)
}
for _, f := range qrImages {
err = os.Remove(f)
if err != nil {
return err
}
}
err = service.WaCli.Logout()
return
}
func (service AppServiceImpl) Reconnect(c *fiber.Ctx) (err error) {
service.WaCli.Disconnect()
return service.WaCli.Connect()
}

312
src/services/send_service.go

@ -1,14 +1,310 @@
package services package services
import ( import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
"github.com/gofiber/fiber/v2"
"context"
"errors"
"fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
fiberUtils "github.com/gofiber/fiber/v2/utils"
"github.com/h2non/bimg"
"github.com/valyala/fasthttp"
"go.mau.fi/whatsmeow"
waProto "go.mau.fi/whatsmeow/binary/proto"
"google.golang.org/protobuf/proto"
"net/http"
"os"
"os/exec"
) )
type SendService interface {
SendText(c *fiber.Ctx, request structs.SendMessageRequest) (response structs.SendMessageResponse, err error)
SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, err error)
SendFile(c *fiber.Ctx, request structs.SendFileRequest) (response structs.SendFileResponse, err error)
SendVideo(c *fiber.Ctx, request structs.SendVideoRequest) (response structs.SendVideoResponse, err error)
SendContact(c *fiber.Ctx, request structs.SendContactRequest) (response structs.SendContactResponse, err error)
type serviceSend struct {
WaCli *whatsmeow.Client
}
func NewSendService(waCli *whatsmeow.Client) domainSend.ISendService {
return &serviceSend{
WaCli: waCli,
}
}
func (service serviceSend) SendText(ctx context.Context, request domainSend.MessageRequest) (response domainSend.MessageResponse, err error) {
utils.MustLogin(service.WaCli)
recipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
msg := &waProto.Message{Conversation: proto.String(request.Message)}
ts, err := service.WaCli.SendMessage(ctx, recipient, "", msg)
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts)
}
return response, nil
}
func (service serviceSend) SendImage(ctx context.Context, request domainSend.ImageRequest) (response domainSend.ImageResponse, err error) {
utils.MustLogin(service.WaCli)
var (
imagePath string
imageThumbnail string
deletedItems []string
)
// Save image to server
oriImagePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.Image.Filename)
err = fasthttp.SaveMultipartFile(request.Image, oriImagePath)
if err != nil {
return response, err
}
deletedItems = append(deletedItems, oriImagePath)
// Generate thumbnail with smalled image
openThumbnailBuffer, err := bimg.Read(oriImagePath)
imageThumbnail = fmt.Sprintf("%s/thumbnails-%s", config.PathSendItems, request.Image.Filename)
thumbnailImage, err := bimg.NewImage(openThumbnailBuffer).Process(bimg.Options{Quality: 90, Width: 100, Embed: true})
if err != nil {
return response, err
}
err = bimg.Write(imageThumbnail, thumbnailImage)
if err != nil {
return response, err
}
deletedItems = append(deletedItems, imageThumbnail)
if request.Compress {
// Resize image
openImageBuffer, err := bimg.Read(oriImagePath)
newImage, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Embed: true})
if err != nil {
return response, err
}
newImagePath := fmt.Sprintf("%s/new-%s", config.PathSendItems, request.Image.Filename)
err = bimg.Write(newImagePath, newImage)
if err != nil {
return response, err
}
deletedItems = append(deletedItems, newImagePath)
imagePath = newImagePath
} else {
imagePath = oriImagePath
}
// Send to WA server
dataWaCaption := request.Caption
dataWaRecipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
dataWaImage, err := os.ReadFile(imagePath)
if err != nil {
return response, err
}
uploadedImage, err := service.WaCli.Upload(context.Background(), dataWaImage, whatsmeow.MediaImage)
if err != nil {
fmt.Printf("Failed to upload file: %v", err)
return response, err
}
dataWaThumbnail, err := os.ReadFile(imageThumbnail)
msg := &waProto.Message{ImageMessage: &waProto.ImageMessage{
JpegThumbnail: dataWaThumbnail,
Caption: proto.String(dataWaCaption),
Url: proto.String(uploadedImage.URL),
DirectPath: proto.String(uploadedImage.DirectPath),
MediaKey: uploadedImage.MediaKey,
Mimetype: proto.String(http.DetectContentType(dataWaImage)),
FileEncSha256: uploadedImage.FileEncSHA256,
FileSha256: uploadedImage.FileSHA256,
FileLength: proto.Uint64(uint64(len(dataWaImage))),
ViewOnce: proto.Bool(request.ViewOnce),
}}
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg)
go func() {
errDelete := utils.RemoveFile(0, deletedItems...)
if errDelete != nil {
fmt.Println("error when deleting picture: ", errDelete)
}
}()
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
}
}
func (service serviceSend) SendFile(ctx context.Context, request domainSend.FileRequest) (response domainSend.FileResponse, err error) {
utils.MustLogin(service.WaCli)
oriFilePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.File.Filename)
err = fasthttp.SaveMultipartFile(request.File, oriFilePath)
if err != nil {
return response, err
}
// Send to WA server
dataWaRecipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
dataWaFile, err := os.ReadFile(oriFilePath)
if err != nil {
return response, err
}
uploadedFile, err := service.WaCli.Upload(context.Background(), dataWaFile, whatsmeow.MediaDocument)
if err != nil {
fmt.Printf("Failed to upload file: %v", err)
return response, err
}
msg := &waProto.Message{DocumentMessage: &waProto.DocumentMessage{
Url: proto.String(uploadedFile.URL),
Mimetype: proto.String(http.DetectContentType(dataWaFile)),
Title: proto.String(request.File.Filename),
FileSha256: uploadedFile.FileSHA256,
FileLength: proto.Uint64(uploadedFile.FileLength),
MediaKey: uploadedFile.MediaKey,
FileName: proto.String(request.File.Filename),
FileEncSha256: uploadedFile.FileEncSHA256,
DirectPath: proto.String(uploadedFile.DirectPath),
}}
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg)
go func() {
errDelete := utils.RemoveFile(0, oriFilePath)
if errDelete != nil {
fmt.Println(errDelete)
}
}()
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Document sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
}
}
func (service serviceSend) SendVideo(ctx context.Context, request domainSend.VideoRequest) (response domainSend.VideoResponse, err error) {
utils.MustLogin(service.WaCli)
var (
videoPath string
videoThumbnail string
deletedItems []string
)
generateUUID := fiberUtils.UUIDv4()
// Save video to server
oriVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+request.Video.Filename)
err = fasthttp.SaveMultipartFile(request.Video, oriVideoPath)
if err != nil {
return response, err
}
// Get thumbnail video with ffmpeg
thumbnailVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+".png")
cmdThumbnail := exec.Command("ffmpeg", "-i", oriVideoPath, "-ss", "00:00:01.000", "-vframes", "1", thumbnailVideoPath)
err = cmdThumbnail.Run()
utils.PanicIfNeeded(err, "error when getting thumbnail")
// Resize Thumbnail
openImageBuffer, err := bimg.Read(thumbnailVideoPath)
resize, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Embed: true})
if err != nil {
return response, err
}
thumbnailResizeVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+"_resize.png")
err = bimg.Write(thumbnailResizeVideoPath, resize)
if err != nil {
return response, err
}
deletedItems = append(deletedItems, thumbnailVideoPath)
deletedItems = append(deletedItems, thumbnailResizeVideoPath)
videoThumbnail = thumbnailResizeVideoPath
if request.Compress {
compresVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+".mp4")
// Compress video with ffmpeg
cmdCompress := exec.Command("ffmpeg", "-i", oriVideoPath, "-strict", "-2", compresVideoPath)
err = cmdCompress.Run()
utils.PanicIfNeeded(err, "error when compress video")
videoPath = compresVideoPath
deletedItems = append(deletedItems, compresVideoPath)
} else {
videoPath = oriVideoPath
deletedItems = append(deletedItems, oriVideoPath)
}
//Send to WA server
dataWaRecipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
dataWaVideo, err := os.ReadFile(videoPath)
if err != nil {
return response, err
}
uploadedFile, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo)
if err != nil {
fmt.Printf("Failed to upload file: %v", err)
return response, err
}
dataWaThumbnail, err := os.ReadFile(videoThumbnail)
if err != nil {
return response, err
}
msg := &waProto.Message{VideoMessage: &waProto.VideoMessage{
Url: proto.String(uploadedFile.URL),
Mimetype: proto.String(http.DetectContentType(dataWaVideo)),
Caption: proto.String(request.Caption),
FileLength: proto.Uint64(uploadedFile.FileLength),
FileSha256: uploadedFile.FileSHA256,
FileEncSha256: uploadedFile.FileEncSHA256,
MediaKey: uploadedFile.MediaKey,
DirectPath: proto.String(uploadedFile.DirectPath),
ViewOnce: proto.Bool(request.ViewOnce),
JpegThumbnail: dataWaThumbnail,
}}
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg)
go func() {
errDelete := utils.RemoveFile(0, deletedItems...)
if errDelete != nil {
fmt.Println(errDelete)
}
}()
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Video sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
}
}
func (service serviceSend) SendContact(ctx context.Context, request domainSend.ContactRequest) (response domainSend.ContactResponse, err error) {
utils.MustLogin(service.WaCli)
recipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
msgVCard := fmt.Sprintf("BEGIN:VCARD\nVERSION:3.0\nN:;%v;;;\nFN:%v\nTEL;type=CELL;waid=%v:+%v\nEND:VCARD",
request.ContactName, request.ContactName, request.ContactPhone, request.ContactPhone)
msg := &waProto.Message{ContactMessage: &waProto.ContactMessage{
DisplayName: proto.String(request.ContactName),
Vcard: proto.String(msgVCard),
}}
ts, err := service.WaCli.SendMessage(ctx, recipient, "", msg)
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Contact sent to %s (server timestamp: %s)", request.Phone, ts)
}
return response, nil
} }

310
src/services/send_service_impl.go

@ -1,310 +0,0 @@
package services
import (
"context"
"errors"
"fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/gofiber/fiber/v2"
fiberUtils "github.com/gofiber/fiber/v2/utils"
"github.com/h2non/bimg"
"go.mau.fi/whatsmeow"
waProto "go.mau.fi/whatsmeow/binary/proto"
"google.golang.org/protobuf/proto"
"net/http"
"os"
"os/exec"
)
type SendServiceImpl struct {
WaCli *whatsmeow.Client
}
func NewSendService(waCli *whatsmeow.Client) SendService {
return &SendServiceImpl{
WaCli: waCli,
}
}
func (service SendServiceImpl) SendText(c *fiber.Ctx, request structs.SendMessageRequest) (response structs.SendMessageResponse, err error) {
utils.MustLogin(service.WaCli)
recipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
msg := &waProto.Message{Conversation: proto.String(request.Message)}
ts, err := service.WaCli.SendMessage(c.Context(), recipient, "", msg)
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts)
}
return response, nil
}
func (service SendServiceImpl) SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, err error) {
utils.MustLogin(service.WaCli)
var (
imagePath string
imageThumbnail string
deletedItems []string
)
// Save image to server
oriImagePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.Image.Filename)
err = c.SaveFile(request.Image, oriImagePath)
if err != nil {
return response, err
}
deletedItems = append(deletedItems, oriImagePath)
// Generate thumbnail with smalled image
openThumbnailBuffer, err := bimg.Read(oriImagePath)
imageThumbnail = fmt.Sprintf("%s/thumbnails-%s", config.PathSendItems, request.Image.Filename)
thumbnailImage, err := bimg.NewImage(openThumbnailBuffer).Process(bimg.Options{Quality: 90, Width: 100, Embed: true})
if err != nil {
return response, err
}
err = bimg.Write(imageThumbnail, thumbnailImage)
if err != nil {
return response, err
}
deletedItems = append(deletedItems, imageThumbnail)
if request.Compress {
// Resize image
openImageBuffer, err := bimg.Read(oriImagePath)
newImage, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Embed: true})
if err != nil {
return response, err
}
newImagePath := fmt.Sprintf("%s/new-%s", config.PathSendItems, request.Image.Filename)
err = bimg.Write(newImagePath, newImage)
if err != nil {
return response, err
}
deletedItems = append(deletedItems, newImagePath)
imagePath = newImagePath
} else {
imagePath = oriImagePath
}
// Send to WA server
dataWaCaption := request.Caption
dataWaRecipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
dataWaImage, err := os.ReadFile(imagePath)
if err != nil {
return response, err
}
uploadedImage, err := service.WaCli.Upload(context.Background(), dataWaImage, whatsmeow.MediaImage)
if err != nil {
fmt.Printf("Failed to upload file: %v", err)
return response, err
}
dataWaThumbnail, err := os.ReadFile(imageThumbnail)
msg := &waProto.Message{ImageMessage: &waProto.ImageMessage{
JpegThumbnail: dataWaThumbnail,
Caption: proto.String(dataWaCaption),
Url: proto.String(uploadedImage.URL),
DirectPath: proto.String(uploadedImage.DirectPath),
MediaKey: uploadedImage.MediaKey,
Mimetype: proto.String(http.DetectContentType(dataWaImage)),
FileEncSha256: uploadedImage.FileEncSHA256,
FileSha256: uploadedImage.FileSHA256,
FileLength: proto.Uint64(uint64(len(dataWaImage))),
ViewOnce: proto.Bool(request.ViewOnce),
}}
ts, err := service.WaCli.SendMessage(c.Context(), dataWaRecipient, "", msg)
go func() {
errDelete := utils.RemoveFile(0, deletedItems...)
if errDelete != nil {
fmt.Println("error when deleting picture: ", errDelete)
}
}()
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
}
}
func (service SendServiceImpl) SendFile(c *fiber.Ctx, request structs.SendFileRequest) (response structs.SendFileResponse, err error) {
utils.MustLogin(service.WaCli)
oriFilePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.File.Filename)
err = c.SaveFile(request.File, oriFilePath)
if err != nil {
return response, err
}
// Send to WA server
dataWaRecipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
dataWaFile, err := os.ReadFile(oriFilePath)
if err != nil {
return response, err
}
uploadedFile, err := service.WaCli.Upload(context.Background(), dataWaFile, whatsmeow.MediaDocument)
if err != nil {
fmt.Printf("Failed to upload file: %v", err)
return response, err
}
msg := &waProto.Message{DocumentMessage: &waProto.DocumentMessage{
Url: proto.String(uploadedFile.URL),
Mimetype: proto.String(http.DetectContentType(dataWaFile)),
Title: proto.String(request.File.Filename),
FileSha256: uploadedFile.FileSHA256,
FileLength: proto.Uint64(uploadedFile.FileLength),
MediaKey: uploadedFile.MediaKey,
FileName: proto.String(request.File.Filename),
FileEncSha256: uploadedFile.FileEncSHA256,
DirectPath: proto.String(uploadedFile.DirectPath),
}}
ts, err := service.WaCli.SendMessage(c.Context(), dataWaRecipient, "", msg)
go func() {
errDelete := utils.RemoveFile(0, oriFilePath)
if errDelete != nil {
fmt.Println(errDelete)
}
}()
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Document sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
}
}
func (service SendServiceImpl) SendVideo(c *fiber.Ctx, request structs.SendVideoRequest) (response structs.SendVideoResponse, err error) {
utils.MustLogin(service.WaCli)
var (
videoPath string
videoThumbnail string
deletedItems []string
)
generateUUID := fiberUtils.UUIDv4()
// Save video to server
oriVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+request.Video.Filename)
err = c.SaveFile(request.Video, oriVideoPath)
if err != nil {
return response, err
}
// Get thumbnail video with ffmpeg
thumbnailVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+".png")
cmdThumbnail := exec.Command("ffmpeg", "-i", oriVideoPath, "-ss", "00:00:01.000", "-vframes", "1", thumbnailVideoPath)
err = cmdThumbnail.Run()
utils.PanicIfNeeded(err, "error when getting thumbnail")
// Resize Thumbnail
openImageBuffer, err := bimg.Read(thumbnailVideoPath)
resize, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Embed: true})
if err != nil {
return response, err
}
thumbnailResizeVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+"_resize.png")
err = bimg.Write(thumbnailResizeVideoPath, resize)
if err != nil {
return response, err
}
deletedItems = append(deletedItems, thumbnailVideoPath)
deletedItems = append(deletedItems, thumbnailResizeVideoPath)
videoThumbnail = thumbnailResizeVideoPath
if request.Compress {
compresVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+".mp4")
// Compress video with ffmpeg
cmdCompress := exec.Command("ffmpeg", "-i", oriVideoPath, "-strict", "-2", compresVideoPath)
err = cmdCompress.Run()
utils.PanicIfNeeded(err, "error when compress video")
videoPath = compresVideoPath
deletedItems = append(deletedItems, compresVideoPath)
} else {
videoPath = oriVideoPath
deletedItems = append(deletedItems, oriVideoPath)
}
//Send to WA server
dataWaRecipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
dataWaVideo, err := os.ReadFile(videoPath)
if err != nil {
return response, err
}
uploadedFile, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo)
if err != nil {
fmt.Printf("Failed to upload file: %v", err)
return response, err
}
dataWaThumbnail, err := os.ReadFile(videoThumbnail)
if err != nil {
return response, err
}
msg := &waProto.Message{VideoMessage: &waProto.VideoMessage{
Url: proto.String(uploadedFile.URL),
Mimetype: proto.String(http.DetectContentType(dataWaVideo)),
Caption: proto.String(request.Caption),
FileLength: proto.Uint64(uploadedFile.FileLength),
FileSha256: uploadedFile.FileSHA256,
FileEncSha256: uploadedFile.FileEncSHA256,
MediaKey: uploadedFile.MediaKey,
DirectPath: proto.String(uploadedFile.DirectPath),
ViewOnce: proto.Bool(request.ViewOnce),
JpegThumbnail: dataWaThumbnail,
}}
ts, err := service.WaCli.SendMessage(c.Context(), dataWaRecipient, "", msg)
go func() {
errDelete := utils.RemoveFile(0, deletedItems...)
if errDelete != nil {
fmt.Println(errDelete)
}
}()
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Video sent to %s (server timestamp: %s)", request.Phone, ts)
return response, nil
}
}
func (service SendServiceImpl) SendContact(c *fiber.Ctx, request structs.SendContactRequest) (response structs.SendContactResponse, err error) {
utils.MustLogin(service.WaCli)
recipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
msgVCard := fmt.Sprintf("BEGIN:VCARD\nVERSION:3.0\nN:;%v;;;\nFN:%v\nTEL;type=CELL;waid=%v:+%v\nEND:VCARD",
request.ContactName, request.ContactName, request.ContactPhone, request.ContactPhone)
msg := &waProto.Message{ContactMessage: &waProto.ContactMessage{
DisplayName: proto.String(request.ContactName),
Vcard: proto.String(msgVCard),
}}
ts, err := service.WaCli.SendMessage(c.Context(), recipient, "", msg)
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Contact sent to %s (server timestamp: %s)", request.Phone, ts)
}
return response, nil
}

115
src/services/user_service.go

@ -1,13 +1,114 @@
package services package services
import ( import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
"github.com/gofiber/fiber/v2"
"context"
"errors"
"fmt"
domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/types"
) )
type UserService interface {
UserInfo(c *fiber.Ctx, request structs.UserInfoRequest) (response structs.UserInfoResponse, err error)
UserAvatar(c *fiber.Ctx, request structs.UserAvatarRequest) (response structs.UserAvatarResponse, err error)
UserMyListGroups(c *fiber.Ctx) (response structs.UserMyListGroupsResponse, err error)
UserMyPrivacySetting(c *fiber.Ctx) (response structs.UserMyPrivacySettingResponse, err error)
type userService struct {
WaCli *whatsmeow.Client
}
func NewUserService(waCli *whatsmeow.Client) domainUser.IUserService {
return &userService{
WaCli: waCli,
}
}
func (service userService) Info(_ context.Context, request domainUser.InfoRequest) (response domainUser.InfoResponse, err error) {
utils.MustLogin(service.WaCli)
var jids []types.JID
jid, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
jids = append(jids, jid)
resp, err := service.WaCli.GetUserInfo(jids)
if err != nil {
return response, err
}
for _, userInfo := range resp {
var device []domainUser.InfoResponseDataDevice
for _, j := range userInfo.Devices {
device = append(device, domainUser.InfoResponseDataDevice{
User: j.User,
Agent: j.Agent,
Device: utils.GetPlatformName(int(j.Device)),
Server: j.Server,
AD: j.AD,
})
}
data := domainUser.InfoResponseData{
Status: userInfo.Status,
PictureID: userInfo.PictureID,
Devices: device,
}
if userInfo.VerifiedName != nil {
data.VerifiedName = fmt.Sprintf("%v", *userInfo.VerifiedName)
}
response.Data = append(response.Data, data)
}
return response, nil
}
func (service userService) Avatar(_ context.Context, request domainUser.AvatarRequest) (response domainUser.AvatarResponse, err error) {
utils.MustLogin(service.WaCli)
jid, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
pic, err := service.WaCli.GetProfilePictureInfo(jid, false, "")
if err != nil {
return response, err
} else if pic == nil {
return response, errors.New("no avatar found")
} else {
response.URL = pic.URL
response.ID = pic.ID
response.Type = pic.Type
return response, nil
}
}
func (service userService) MyListGroups(_ context.Context) (response domainUser.MyListGroupsResponse, err error) {
utils.MustLogin(service.WaCli)
groups, err := service.WaCli.GetJoinedGroups()
if err != nil {
return
}
fmt.Printf("%+v\n", groups)
if groups != nil {
for _, group := range groups {
response.Data = append(response.Data, *group)
}
}
return response, nil
}
func (service userService) MyPrivacySetting(_ context.Context) (response domainUser.MyPrivacySettingResponse, err error) {
utils.MustLogin(service.WaCli)
resp, err := service.WaCli.TryFetchPrivacySettings(false)
if err != nil {
return
}
response.GroupAdd = string(resp.GroupAdd)
response.Status = string(resp.Status)
response.ReadReceipts = string(resp.ReadReceipts)
response.Profile = string(resp.Profile)
return response, nil
} }

114
src/services/user_service_impl.go

@ -1,114 +0,0 @@
package services
import (
"errors"
"fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/gofiber/fiber/v2"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/types"
)
type UserServiceImpl struct {
WaCli *whatsmeow.Client
}
func NewUserService(waCli *whatsmeow.Client) UserService {
return &UserServiceImpl{
WaCli: waCli,
}
}
func (service UserServiceImpl) UserInfo(_ *fiber.Ctx, request structs.UserInfoRequest) (response structs.UserInfoResponse, err error) {
utils.MustLogin(service.WaCli)
var jids []types.JID
jid, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
jids = append(jids, jid)
resp, err := service.WaCli.GetUserInfo(jids)
if err != nil {
return response, err
}
for _, userInfo := range resp {
var device []structs.UserInfoResponseDataDevice
for _, j := range userInfo.Devices {
device = append(device, structs.UserInfoResponseDataDevice{
User: j.User,
Agent: j.Agent,
Device: utils.GetPlatformName(int(j.Device)),
Server: j.Server,
AD: j.AD,
})
}
data := structs.UserInfoResponseData{
Status: userInfo.Status,
PictureID: userInfo.PictureID,
Devices: device,
}
if userInfo.VerifiedName != nil {
data.VerifiedName = fmt.Sprintf("%v", *userInfo.VerifiedName)
}
response.Data = append(response.Data, data)
}
return response, nil
}
func (service UserServiceImpl) UserAvatar(c *fiber.Ctx, request structs.UserAvatarRequest) (response structs.UserAvatarResponse, err error) {
utils.MustLogin(service.WaCli)
jid, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
pic, err := service.WaCli.GetProfilePictureInfo(jid, false, "")
if err != nil {
return response, err
} else if pic == nil {
return response, errors.New("no avatar found")
} else {
response.URL = pic.URL
response.ID = pic.ID
response.Type = pic.Type
return response, nil
}
}
func (service UserServiceImpl) UserMyListGroups(_ *fiber.Ctx) (response structs.UserMyListGroupsResponse, err error) {
utils.MustLogin(service.WaCli)
groups, err := service.WaCli.GetJoinedGroups()
if err != nil {
return
}
fmt.Printf("%+v\n", groups)
if groups != nil {
for _, group := range groups {
response.Data = append(response.Data, *group)
}
}
return response, nil
}
func (service UserServiceImpl) UserMyPrivacySetting(_ *fiber.Ctx) (response structs.UserMyPrivacySettingResponse, err error) {
utils.MustLogin(service.WaCli)
resp, err := service.WaCli.TryFetchPrivacySettings(false)
if err != nil {
return
}
response.GroupAdd = string(resp.GroupAdd)
response.Status = string(resp.Status)
response.ReadReceipts = string(resp.ReadReceipts)
response.Profile = string(resp.Profile)
return response, nil
}

67
src/structs/send_struct.go

@ -1,67 +0,0 @@
package structs
import (
"mime/multipart"
)
type SendType string
const TypeUser SendType = "user"
const TypeGroup SendType = "group"
type SendMessageRequest struct {
Phone string `json:"phone" form:"phone"`
Message string `json:"message" form:"message"`
Type SendType `json:"type" form:"type"`
}
type SendMessageResponse struct {
Status string `json:"status"`
}
type SendImageRequest struct {
Phone string `json:"phone" form:"phone"`
Caption string `json:"caption" form:"caption"`
Image *multipart.FileHeader `json:"image" form:"image"`
ViewOnce bool `json:"view_once" form:"view_once"`
Type SendType `json:"type" form:"type"`
Compress bool `json:"compress"`
}
type SendImageResponse struct {
Status string `json:"status"`
}
type SendFileRequest struct {
Phone string `json:"phone" form:"phone"`
File *multipart.FileHeader `json:"file" form:"file"`
Type SendType `json:"type" form:"type"`
}
type SendFileResponse struct {
Status string `json:"status"`
}
type SendVideoRequest struct {
Phone string `json:"phone" form:"phone"`
Caption string `json:"caption" form:"caption"`
Video *multipart.FileHeader `json:"video" form:"video"`
Type SendType `json:"type" form:"type"`
ViewOnce bool `json:"view_once" form:"view_once"`
Compress bool `json:"compress"`
}
type SendVideoResponse struct {
Status string `json:"status"`
}
type SendContactRequest struct {
Phone string `json:"phone" form:"phone"`
ContactName string `json:"contact_name" form:"contact_name"`
ContactPhone string `json:"contact_phone" form:"contact_phone"`
Type SendType `json:"type" form:"type"`
}
type SendContactResponse struct {
Status string `json:"status"`
}

48
src/structs/user_struct.go

@ -1,48 +0,0 @@
package structs
import "go.mau.fi/whatsmeow/types"
type UserInfoRequest struct {
Phone string `json:"phone" query:"phone"`
}
type UserInfoResponseDataDevice struct {
User string
Agent uint8
Device string
Server string
AD bool
}
type UserInfoResponseData struct {
VerifiedName string `json:"verified_name"`
Status string `json:"status"`
PictureID string `json:"picture_id"`
Devices []UserInfoResponseDataDevice `json:"devices"`
}
type UserInfoResponse struct {
Data []UserInfoResponseData `json:"data"`
}
type UserAvatarRequest struct {
Phone string `json:"phone" query:"phone"`
}
type UserAvatarResponse struct {
URL string `json:"url"`
ID string `json:"id"`
Type string `json:"type"`
}
type UserMyPrivacySettingResponse struct {
GroupAdd string `json:"group_add"`
LastSeen string `json:"last_seen"`
Status string `json:"status"`
Profile string `json:"profile"`
ReadReceipts string `json:"read_receipts"`
}
type UserMyListGroupsResponse struct {
Data []types.GroupInfo `json:"data"`
}

2
src/utils/whatsapp.go

@ -108,6 +108,8 @@ func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client {
store.DeviceProps.PlatformType = &config.AppPlatform store.DeviceProps.PlatformType = &config.AppPlatform
store.DeviceProps.Os = &osName store.DeviceProps.Os = &osName
cli = whatsmeow.NewClient(device, waLog.Stdout("Client", config.WhatsappLogLevel, true)) cli = whatsmeow.NewClient(device, waLog.Stdout("Client", config.WhatsappLogLevel, true))
cli.EnableAutoReconnect = true
cli.AutoTrustIdentity = true
cli.AddEventHandler(handler) cli.AddEventHandler(handler)
return cli return cli

12
src/validations/send_validation.go

@ -3,13 +3,13 @@ package validations
import ( import (
"fmt" "fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config" "github.com/aldinokemal/go-whatsapp-web-multidevice/config"
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
) )
func ValidateSendMessage(request structs.SendMessageRequest) {
func ValidateSendMessage(request domainSend.MessageRequest) {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)),
validation.Field(&request.Message, validation.Required, validation.Length(1, 1000)), validation.Field(&request.Message, validation.Required, validation.Length(1, 1000)),
@ -22,7 +22,7 @@ func ValidateSendMessage(request structs.SendMessageRequest) {
} }
} }
func ValidateSendImage(request structs.SendImageRequest) {
func ValidateSendImage(request domainSend.ImageRequest) {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)),
validation.Field(&request.Caption, validation.When(true, validation.Length(1, 1000))), validation.Field(&request.Caption, validation.When(true, validation.Length(1, 1000))),
@ -48,7 +48,7 @@ func ValidateSendImage(request structs.SendImageRequest) {
} }
} }
func ValidateSendFile(request structs.SendFileRequest) {
func ValidateSendFile(request domainSend.FileRequest) {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)),
validation.Field(&request.File, validation.Required), validation.Field(&request.File, validation.Required),
@ -68,7 +68,7 @@ func ValidateSendFile(request structs.SendFileRequest) {
} }
} }
func ValidateSendVideo(request structs.SendVideoRequest) {
func ValidateSendVideo(request domainSend.VideoRequest) {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)),
validation.Field(&request.Video, validation.Required), validation.Field(&request.Video, validation.Required),
@ -100,7 +100,7 @@ func ValidateSendVideo(request structs.SendVideoRequest) {
} }
} }
func ValidateSendContact(request structs.SendContactRequest) {
func ValidateSendContact(request domainSend.ContactRequest) {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)),
validation.Field(&request.ContactName, validation.Required), validation.Field(&request.ContactName, validation.Required),

6
src/validations/send_validation_test.go

@ -1,14 +1,14 @@
package validations package validations
import ( import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
) )
func TestValidateSendMessage(t *testing.T) { func TestValidateSendMessage(t *testing.T) {
type args struct { type args struct {
request structs.SendMessageRequest
request domainSend.MessageRequest
} }
tests := []struct { tests := []struct {
name string name string
@ -17,7 +17,7 @@ func TestValidateSendMessage(t *testing.T) {
}{ }{
{ {
name: "success phone & message normal", name: "success phone & message normal",
args: args{request: structs.SendMessageRequest{
args: args{request: domainSend.MessageRequest{
Phone: "6289685024091", Phone: "6289685024091",
Message: "Hello this is testing", Message: "Hello this is testing",
}}, }},

6
src/validations/user_validation.go

@ -1,13 +1,13 @@
package validations package validations
import ( import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs"
domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is" "github.com/go-ozzo/ozzo-validation/v4/is"
) )
func ValidateUserInfo(request structs.UserInfoRequest) {
func ValidateUserInfo(request domainUser.InfoRequest) {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required, is.E164, validation.Length(10, 15)), validation.Field(&request.Phone, validation.Required, is.E164, validation.Length(10, 15)),
) )
@ -18,7 +18,7 @@ func ValidateUserInfo(request structs.UserInfoRequest) {
}) })
} }
} }
func ValidateUserAvatar(request structs.UserAvatarRequest) {
func ValidateUserAvatar(request domainUser.AvatarRequest) {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required, is.E164, validation.Length(10, 15)), validation.Field(&request.Phone, validation.Required, is.E164, validation.Length(10, 15)),
) )

4
src/views/index.html

@ -108,7 +108,7 @@
</div> </div>
<div class="green card" @click="sendContactModal('user')" style="cursor: pointer"> <div class="green card" @click="sendContactModal('user')" style="cursor: pointer">
<div class="content"> <div class="content">
<a class="ui blue right ribbon label">Contact</a>
<a class="ui blue right ribbon label">Private</a>
<div class="header">Send Contact</div> <div class="header">Send Contact</div>
<div class="description"> <div class="description">
Send contact to any whatsapp number Send contact to any whatsapp number
@ -166,7 +166,7 @@
</div> </div>
<div class="green card" @click="sendContactModal('group')" style="cursor: pointer"> <div class="green card" @click="sendContactModal('group')" style="cursor: pointer">
<div class="content"> <div class="content">
<a class="ui teal right ribbon label">Contact</a>
<a class="ui teal right ribbon label">Group</a>
<div class="header">Send Contact</div> <div class="header">Send Contact</div>
<div class="description"> <div class="description">
Send contact to any whatsapp number Send contact to any whatsapp number

Loading…
Cancel
Save