diff --git a/.gitignore b/.gitignore index 56f8ffa..be31772 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ tmp .idea .DS_Store history-*.json +main \ No newline at end of file diff --git a/config/settings.go b/config/settings.go index 181e7bf..9395424 100644 --- a/config/settings.go +++ b/config/settings.go @@ -1,4 +1,5 @@ package config const PathQrCode = "statics/images/qrcode" +const PathSendImage = "statics/images/sendimages" const DBName = "hydrogenWaCli.db" diff --git a/controllers/app_controller.go b/controllers/app_controller.go index 2a4e020..5c0baf6 100644 --- a/controllers/app_controller.go +++ b/controllers/app_controller.go @@ -46,7 +46,7 @@ func (controller *AppController) Logout(c *fiber.Ctx) error { } func (controller *AppController) Reconnect(c *fiber.Ctx) error { - err := controller.Service.Logout(c) + err := controller.Service.Reconnect(c) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ diff --git a/controllers/send_controller.go b/controllers/send_controller.go index bf3b057..5ebb858 100644 --- a/controllers/send_controller.go +++ b/controllers/send_controller.go @@ -18,6 +18,7 @@ func NewSendController(service services.SendService) SendController { func (controller *SendController) Route(app *fiber.App) { app.Post("/send/message", controller.SendText) + app.Post("/send/image", controller.SendImage) } func (controller *SendController) SendText(c *fiber.Ctx) error { @@ -38,3 +39,27 @@ func (controller *SendController) SendText(c *fiber.Ctx) error { Results: response, }) } + +func (controller *SendController) SendImage(c *fiber.Ctx) error { + var request structs.SendImageRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + file, err := c.FormFile("image") + utils.PanicIfNeeded(err) + + request.Image = file + + //add validation send image + validations.ValidateSendImage(request) + + request.PhoneNumber = request.PhoneNumber + "@s.whatsapp.net" + response, err := controller.Service.SendImage(c, request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Code: 200, + Message: "Success", + Results: response, + }) +} diff --git a/go.mod b/go.mod index 069b24b..a6bb579 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/gofiber/fiber/v2 v2.26.0 github.com/gofiber/template v1.6.21 + github.com/h2non/bimg v1.1.6 github.com/mattn/go-sqlite3 v1.14.11 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e go.mau.fi/whatsmeow v0.0.0-20220204210537-a425ddb0b16c diff --git a/go.sum b/go.sum index 3ba7f50..d4400cc 100644 --- a/go.sum +++ b/go.sum @@ -164,6 +164,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/h2non/bimg v1.1.6 h1:W/kYCxx5PjiJJrYCB60CLDpm5pl0HJv3hthNp9II9oY= +github.com/h2non/bimg v1.1.6/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/readme.md b/readme.md index 97ca836..2223f10 100644 --- a/readme.md +++ b/readme.md @@ -10,8 +10,22 @@ You can fork or edit this source code ! Current API -| Menu | Method | URL | parameter | type | -|---------------------|--------|---------------|----------------------------------------|-----------| -| Login | GET | /auth/login | | | -| Logout | GET | /auth/logout | | | -| Send Message (Text) | POST | /send/message | phone_number (62...), message (string) | form-data | \ No newline at end of file +| Feature | Menu | Method | URL | parameter | type | +|----------|----------------------|--------|----------------|--------------------------------------------------------------------------|-------------| +| ✅ | Login | GET | /app/login | | | +| ✅ | Logout | GET | /app/logout | | | +| ✅ | Reconnect | GET | /app/reconnect | | | +| ❌ | User Info | GET | /user/info | phone_number (string: 62...) | querystring | +| ❌ | User Avatar | GET | /user/avatar | phone_number (string: 62...) | querystring | +| ✅ | Send Message (Text) | POST | /send/message | phone_number (string: 62...)
message (string) | form-data | +| ✅ | Send Message (Image) | POST | /send/message | phone_number (string: 62...)
caption (string)
image (binary) | form-data | + +``` +✅ = Available +❌ = Not Available Yet +``` + +### Mac OS NOTE + +Please do this if you have an error (invalid flag in pkg-config --cflags: +-Xpreprocessor) `export CGO_CFLAGS_ALLOW="-Xpreprocessor"` diff --git a/services/send_service.go b/services/send_service.go index 75c559a..572a952 100644 --- a/services/send_service.go +++ b/services/send_service.go @@ -7,5 +7,5 @@ import ( type SendService interface { SendText(c *fiber.Ctx, request structs.SendMessageRequest) (response structs.SendMessageResponse, err error) - SendImage(c *fiber.Ctx) + SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, err error) } diff --git a/services/send_service_impl.go b/services/send_service_impl.go index 2579a53..6f0c71e 100644 --- a/services/send_service_impl.go +++ b/services/send_service_impl.go @@ -1,14 +1,20 @@ 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" + "github.com/h2non/bimg" "go.mau.fi/whatsmeow" waProto "go.mau.fi/whatsmeow/binary/proto" "google.golang.org/protobuf/proto" + "net/http" + "os" + "time" ) type SendServiceImpl struct { @@ -22,11 +28,11 @@ func NewSendService(waCli *whatsmeow.Client) SendService { } func (service SendServiceImpl) SendText(c *fiber.Ctx, request structs.SendMessageRequest) (response structs.SendMessageResponse, err error) { - msg := &waProto.Message{Conversation: proto.String(request.Message)} recipient, ok := utils.ParseJID(request.PhoneNumber) if !ok { return response, errors.New("invalid JID " + request.PhoneNumber) } + msg := &waProto.Message{Conversation: proto.String(request.Message)} ts, err := service.WaCli.SendMessage(recipient, "", msg) if err != nil { return response, err @@ -36,7 +42,69 @@ func (service SendServiceImpl) SendText(c *fiber.Ctx, request structs.SendMessag return response, nil } -func (service SendServiceImpl) SendImage(c *fiber.Ctx) { - //TODO implement me - panic("implement me") +func (service SendServiceImpl) SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, err error) { + // Resize image + oriImagePath := fmt.Sprintf("%s/%s", config.PathSendImage, request.Image.Filename) + err = c.SaveFile(request.Image, oriImagePath) + if err != nil { + return response, err + } + openImageBuffer, err := bimg.Read(oriImagePath) + newImage, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 400, Height: 400, Embed: true}) + if err != nil { + return response, err + } + + newImagePath := fmt.Sprintf("%s/new-%s", config.PathSendImage, request.Image.Filename) + err = bimg.Write(newImagePath, newImage) + if err != nil { + return response, err + } + + removeFile := func(paths ...string) { + time.Sleep(5 * time.Second) + for _, path := range paths { + err := os.Remove(path) + if err != nil { + fmt.Println("error when delete " + path) + } + } + + } + + // Send to WA server + dataWaCaption := request.Caption + dataWaRecipient, ok := utils.ParseJID(request.PhoneNumber) + if !ok { + return response, errors.New("invalid JID " + request.PhoneNumber) + } + dataWaImage, err := os.ReadFile(newImagePath) + 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 + } + + msg := &waProto.Message{ImageMessage: &waProto.ImageMessage{ + 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(dataWaRecipient, "", msg) + if err != nil { + return response, err + } else { + go removeFile(oriImagePath, newImagePath) + response.Status = fmt.Sprintf("Image message sent (server timestamp: %s)", ts) + return response, nil + } } diff --git a/statics/images/sendimages/.gitignore b/statics/images/sendimages/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/statics/images/sendimages/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/structs/auth_struct.go b/structs/auth_struct.go index 5401b4a..ecf6488 100644 --- a/structs/auth_struct.go +++ b/structs/auth_struct.go @@ -1,18 +1,11 @@ package structs -import "time" +import ( + "time" +) type LoginResponse struct { ImagePath string `json:"image_path"` Duration time.Duration `json:"duration"` Code string `json:"code"` } - -type SendMessageRequest struct { - PhoneNumber string `json:"phone_number" form:"phone_number"` - Message string `json:"message" form:"message"` -} - -type SendMessageResponse struct { - Status string `json:"status"` -} diff --git a/structs/send_struct.go b/structs/send_struct.go new file mode 100644 index 0000000..56a0c8a --- /dev/null +++ b/structs/send_struct.go @@ -0,0 +1,23 @@ +package structs + +import "mime/multipart" + +type SendMessageRequest struct { + PhoneNumber string `json:"phone_number" form:"phone_number"` + Message string `json:"message" form:"message"` +} + +type SendMessageResponse struct { + Status string `json:"status"` +} + +type SendImageRequest struct { + PhoneNumber string `json:"phone_number" form:"phone_number"` + Caption string `json:"caption" form:"caption"` + Image *multipart.FileHeader `json:"image" form:"image"` + ViewOnce bool `json:"view_once" form:"view_once"` +} + +type SendImageResponse struct { + Status string `json:"status"` +} diff --git a/validations/send_validation.go b/validations/send_validation.go index 4cdee04..902819f 100644 --- a/validations/send_validation.go +++ b/validations/send_validation.go @@ -24,3 +24,21 @@ func ValidateSendMessage(request structs.SendMessageRequest) { }) } } + +func ValidateSendImage(request structs.SendImageRequest) { + err := validation.ValidateStruct(&request, + validation.Field(&request.PhoneNumber, validation.Required, is.E164, validation.Length(10, 15)), + validation.Field(&request.Caption, validation.When(true, validation.Length(4, 200))), + validation.Field(&request.Image, validation.Required), + ) + + if err != nil { + panic(utils.ValidationError{ + Message: err.Error(), + }) + } else if !strings.HasPrefix(request.PhoneNumber, "62") { + panic(utils.ValidationError{ + Message: "this is only work for indonesia country (start with 62)", + }) + } +}