diff --git a/src/cmd/root.go b/src/cmd/root.go index 1a6ba6b..7764bc4 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -4,7 +4,7 @@ import ( "encoding/base64" "fmt" "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/services" "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" @@ -63,6 +63,7 @@ func runRest(cmd *cobra.Command, args []string) { }) app.Static("/statics", "./statics") app.Use(middleware.Recovery()) + app.Use(middleware.SelectJid()) if config.AppDebug { app.Use(logger.New()) } @@ -71,19 +72,6 @@ func runRest(cmd *cobra.Command, args []string) { 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 != "" { ba := strings.Split(config.AppBasicAuthCredential, ":") 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 { return ctx.Render("index", fiber.Map{ diff --git a/src/config/settings.go b/src/config/settings.go index b74565a..c23500e 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -11,17 +11,19 @@ var ( AppDebug = false AppOs = fmt.Sprintf("AldinoKemal") AppPlatform = waProto.DeviceProps_PlatformType(1) + AppSelectedDeviceKey = "deviceID" + AppDefaultDevice = "default" AppBasicAuthCredential string PathQrCode = "statics/qrcode" PathSendItems = "statics/senditems" PathStorages = "storages" - DBName = "hydrogenWaCli.db" + DBName = "whatsapp.db" WhatsappLogLevel = "ERROR" WhatsappAutoReplyMessage string WhatsappAutoReplyWebhook string - WhatsappSettingMaxFileSize int64 = 30000000 // 10MB - WhatsappSettingMaxVideoSize int64 = 30000000 // 30MB + WhatsappSettingMaxFileSize int64 = 50000000 // 50MB + WhatsappSettingMaxVideoSize int64 = 100000000 // 100MB ) diff --git a/src/controllers/app_controller.go b/src/controllers/app_controller.go deleted file mode 100644 index ccb2262..0000000 --- a/src/controllers/app_controller.go +++ /dev/null @@ -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, - }) -} diff --git a/src/domains/app/app.go b/src/domains/app/app.go new file mode 100644 index 0000000..03b7be0 --- /dev/null +++ b/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) +} diff --git a/src/domains/app/devices.go b/src/domains/app/devices.go new file mode 100644 index 0000000..cfe9fc2 --- /dev/null +++ b/src/domains/app/devices.go @@ -0,0 +1,6 @@ +package app + +type FetchDevicesResponse struct { + Name string + Device string +} diff --git a/src/structs/auth_struct.go b/src/domains/app/login.go similarity index 81% rename from src/structs/auth_struct.go rename to src/domains/app/login.go index ecf6488..fc8f98b 100644 --- a/src/structs/auth_struct.go +++ b/src/domains/app/login.go @@ -1,8 +1,6 @@ -package structs +package app -import ( - "time" -) +import "time" type LoginResponse struct { ImagePath string `json:"image_path"` diff --git a/src/domains/send/contact.go b/src/domains/send/contact.go new file mode 100644 index 0000000..d6aabe2 --- /dev/null +++ b/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"` +} diff --git a/src/domains/send/file.go b/src/domains/send/file.go new file mode 100644 index 0000000..e05fa9b --- /dev/null +++ b/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"` +} diff --git a/src/domains/send/image.go b/src/domains/send/image.go new file mode 100644 index 0000000..9c15672 --- /dev/null +++ b/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"` +} diff --git a/src/domains/send/send.go b/src/domains/send/send.go new file mode 100644 index 0000000..8e6b8ef --- /dev/null +++ b/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) +} diff --git a/src/domains/send/text.go b/src/domains/send/text.go new file mode 100644 index 0000000..6f49354 --- /dev/null +++ b/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"` +} diff --git a/src/domains/send/video.go b/src/domains/send/video.go new file mode 100644 index 0000000..90cb64e --- /dev/null +++ b/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"` +} diff --git a/src/domains/user/account.go b/src/domains/user/account.go new file mode 100644 index 0000000..2d6fc75 --- /dev/null +++ b/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"` +} diff --git a/src/domains/user/user.go b/src/domains/user/user.go new file mode 100644 index 0000000..dabca89 --- /dev/null +++ b/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) +} diff --git a/src/go.mod b/src/go.mod index e2f2404..da090fc 100644 --- a/src/go.mod +++ b/src/go.mod @@ -13,6 +13,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.0 + github.com/valyala/fasthttp v1.41.0 go.mau.fi/whatsmeow v0.0.0-20221117205735-357841c4ff30 google.golang.org/protobuf v1.28.1 ) @@ -33,7 +34,6 @@ require ( github.com/rivo/uniseg v0.4.2 // indirect github.com/spf13/pflag v1.0.5 // 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 go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf // indirect golang.org/x/crypto v0.1.0 // indirect diff --git a/src/go.sum b/src/go.sum index f695916..1ade34f 100644 --- a/src/go.sum +++ b/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/go.mod h1:vuCfanjqckTuRlqAitJz6QC4ABNnS27wLb816UhsPcc= 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.40.0 h1:fdU7w5hT6PLL7jiWIhtQ+S/k5WEFYoUZidptlPu8GBo= 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/go.mod h1:uk/mUKTXJMBMoETvo/5CA/QlTFERHMooOm0pJPGDGVA= 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.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/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/go.mod h1:2yweL8nczvtlIxkrvCb0y8xiO13rveX9lJPambwYV/E= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= diff --git a/src/internal/rest/app_rest.go b/src/internal/rest/app_rest.go new file mode 100644 index 0000000..f8e0ebc --- /dev/null +++ b/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, + }) +} diff --git a/src/controllers/send_controller.go b/src/internal/rest/send_controller.go similarity index 58% rename from src/controllers/send_controller.go rename to src/internal/rest/send_controller.go index 80be317..81a5c35 100644 --- a/src/controllers/send_controller.go +++ b/src/internal/rest/send_controller.go @@ -1,44 +1,42 @@ -package controllers +package rest 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/validations" "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) utils.PanicIfNeeded(err) // add validation send message validations.ValidateSendMessage(request) - if request.Type == structs.TypeGroup { + if request.Type == domainSend.TypeGroup { request.Phone = request.Phone + "@g.us" } else { 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) 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 err := c.BodyParser(&request) @@ -63,13 +61,13 @@ func (controller *SendController) SendImage(c *fiber.Ctx) error { //add validation send image validations.ValidateSendImage(request) - if request.Type == structs.TypeGroup { + if request.Type == domainSend.TypeGroup { request.Phone = request.Phone + "@g.us" } else { 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) 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) utils.PanicIfNeeded(err) @@ -92,13 +90,13 @@ func (controller *SendController) SendFile(c *fiber.Ctx) error { //add validation send image validations.ValidateSendFile(request) - if request.Type == structs.TypeGroup { + if request.Type == domainSend.TypeGroup { request.Phone = request.Phone + "@g.us" } else { 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) 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) utils.PanicIfNeeded(err) @@ -121,13 +119,13 @@ func (controller *SendController) SendVideo(c *fiber.Ctx) error { //add validation send image validations.ValidateSendVideo(request) - if request.Type == structs.TypeGroup { + if request.Type == domainSend.TypeGroup { request.Phone = request.Phone + "@g.us" } else { 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) 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) utils.PanicIfNeeded(err) // add validation send contect validations.ValidateSendContact(request) - if request.Type == structs.TypeGroup { + if request.Type == domainSend.TypeGroup { request.Phone = request.Phone + "@g.us" } else { 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) return c.JSON(utils.ResponseData{ diff --git a/src/controllers/user_controller.go b/src/internal/rest/user_controller.go similarity index 55% rename from src/controllers/user_controller.go rename to src/internal/rest/user_controller.go index 87cc764..94a0472 100644 --- a/src/controllers/user_controller.go +++ b/src/internal/rest/user_controller.go @@ -1,30 +1,35 @@ -package controllers +package rest 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/validations" "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/avatar", controller.UserAvatar) app.Get("/user/my/privacy", controller.UserMyPrivacySetting) 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) utils.PanicIfNeeded(err) @@ -32,7 +37,7 @@ func (controller *UserController) UserInfo(c *fiber.Ctx) error { validations.ValidateUserInfo(request) 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) 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) utils.PanicIfNeeded(err) @@ -51,7 +56,7 @@ func (controller *UserController) UserAvatar(c *fiber.Ctx) error { validations.ValidateUserAvatar(request) 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) 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) 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) return c.JSON(utils.ResponseData{ diff --git a/src/middleware/select_jid.go b/src/middleware/select_jid.go new file mode 100644 index 0000000..c561bfd --- /dev/null +++ b/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() + } +} diff --git a/src/services/app_service.go b/src/services/app_service.go index 044217b..4f8f09a 100644 --- a/src/services/app_service.go +++ b/src/services/app_service.go @@ -1,12 +1,145 @@ package services 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 } diff --git a/src/services/app_service_impl.go b/src/services/app_service_impl.go deleted file mode 100644 index d1a1f96..0000000 --- a/src/services/app_service_impl.go +++ /dev/null @@ -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() -} diff --git a/src/services/send_service.go b/src/services/send_service.go index f28dd0c..d08456f 100644 --- a/src/services/send_service.go +++ b/src/services/send_service.go @@ -1,14 +1,310 @@ package services 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 } diff --git a/src/services/send_service_impl.go b/src/services/send_service_impl.go deleted file mode 100644 index 3c52eed..0000000 --- a/src/services/send_service_impl.go +++ /dev/null @@ -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 -} diff --git a/src/services/user_service.go b/src/services/user_service.go index fe73846..295b758 100644 --- a/src/services/user_service.go +++ b/src/services/user_service.go @@ -1,13 +1,114 @@ package services 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 } diff --git a/src/services/user_service_impl.go b/src/services/user_service_impl.go deleted file mode 100644 index 244f713..0000000 --- a/src/services/user_service_impl.go +++ /dev/null @@ -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 -} diff --git a/src/structs/send_struct.go b/src/structs/send_struct.go deleted file mode 100644 index 85484ee..0000000 --- a/src/structs/send_struct.go +++ /dev/null @@ -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"` -} diff --git a/src/structs/user_struct.go b/src/structs/user_struct.go deleted file mode 100644 index 12c4ed9..0000000 --- a/src/structs/user_struct.go +++ /dev/null @@ -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"` -} diff --git a/src/utils/whatsapp.go b/src/utils/whatsapp.go index 97f06f1..fe4b546 100644 --- a/src/utils/whatsapp.go +++ b/src/utils/whatsapp.go @@ -108,6 +108,8 @@ func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client { store.DeviceProps.PlatformType = &config.AppPlatform store.DeviceProps.Os = &osName cli = whatsmeow.NewClient(device, waLog.Stdout("Client", config.WhatsappLogLevel, true)) + cli.EnableAutoReconnect = true + cli.AutoTrustIdentity = true cli.AddEventHandler(handler) return cli diff --git a/src/validations/send_validation.go b/src/validations/send_validation.go index 8d799ac..9474232 100644 --- a/src/validations/send_validation.go +++ b/src/validations/send_validation.go @@ -3,13 +3,13 @@ package validations import ( "fmt" "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/dustin/go-humanize" validation "github.com/go-ozzo/ozzo-validation/v4" ) -func ValidateSendMessage(request structs.SendMessageRequest) { +func ValidateSendMessage(request domainSend.MessageRequest) { err := validation.ValidateStruct(&request, validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), 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, validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), 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, validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), 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, validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), 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, validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), validation.Field(&request.ContactName, validation.Required), diff --git a/src/validations/send_validation_test.go b/src/validations/send_validation_test.go index b4ff7d7..ef6da97 100644 --- a/src/validations/send_validation_test.go +++ b/src/validations/send_validation_test.go @@ -1,14 +1,14 @@ package validations import ( - "github.com/aldinokemal/go-whatsapp-web-multidevice/structs" + domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" "github.com/stretchr/testify/assert" "testing" ) func TestValidateSendMessage(t *testing.T) { type args struct { - request structs.SendMessageRequest + request domainSend.MessageRequest } tests := []struct { name string @@ -17,7 +17,7 @@ func TestValidateSendMessage(t *testing.T) { }{ { name: "success phone & message normal", - args: args{request: structs.SendMessageRequest{ + args: args{request: domainSend.MessageRequest{ Phone: "6289685024091", Message: "Hello this is testing", }}, diff --git a/src/validations/user_validation.go b/src/validations/user_validation.go index 5d6e83b..31d46b8 100644 --- a/src/validations/user_validation.go +++ b/src/validations/user_validation.go @@ -1,13 +1,13 @@ package validations 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" validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" ) -func ValidateUserInfo(request structs.UserInfoRequest) { +func ValidateUserInfo(request domainUser.InfoRequest) { err := validation.ValidateStruct(&request, 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, validation.Field(&request.Phone, validation.Required, is.E164, validation.Length(10, 15)), ) diff --git a/src/views/index.html b/src/views/index.html index bbcf2a9..2d06550 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -108,7 +108,7 @@