23 changed files with 620 additions and 575 deletions
-
6src/controllers/app_controller.go
-
29src/controllers/send_controller.go
-
19src/controllers/user_controller.go
-
13src/domains/app/app.go
-
6src/domains/app/login.go
-
12src/domains/send/contact.go
-
13src/domains/send/file.go
-
16src/domains/send/image.go
-
18src/domains/send/send.go
-
11src/domains/send/text.go
-
16src/domains/send/video.go
-
48src/domains/user/account.go
-
12src/domains/user/user.go
-
116src/services/app_service.go
-
118src/services/app_service_impl.go
-
310src/services/send_service.go
-
310src/services/send_service_impl.go
-
13src/services/user_service.go
-
18src/services/user_service_impl.go
-
67src/structs/send_struct.go
-
12src/validations/send_validation.go
-
6src/validations/send_validation_test.go
-
6src/validations/user_validation.go
@ -1,6 +1,15 @@ |
|||
package structs |
|||
package app |
|||
|
|||
import "go.mau.fi/whatsmeow/types" |
|||
import ( |
|||
"github.com/gofiber/fiber/v2" |
|||
"go.mau.fi/whatsmeow/types" |
|||
) |
|||
|
|||
type IAppService interface { |
|||
Login(c *fiber.Ctx) (response LoginResponse, err error) |
|||
Logout(c *fiber.Ctx) (err error) |
|||
Reconnect(c *fiber.Ctx) (err error) |
|||
} |
|||
|
|||
type UserInfoRequest struct { |
|||
Phone string `json:"phone" query:"phone"` |
|||
@ -1,8 +1,6 @@ |
|||
package structs |
|||
package app |
|||
|
|||
import ( |
|||
"time" |
|||
) |
|||
import "time" |
|||
|
|||
type LoginResponse struct { |
|||
ImagePath string `json:"image_path"` |
|||
@ -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"` |
|||
} |
|||
@ -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"` |
|||
} |
|||
@ -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"` |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
package send |
|||
|
|||
import ( |
|||
"github.com/gofiber/fiber/v2" |
|||
) |
|||
|
|||
type Type string |
|||
|
|||
const TypeUser Type = "user" |
|||
const TypeGroup Type = "group" |
|||
|
|||
type ISendService interface { |
|||
SendText(c *fiber.Ctx, request MessageRequest) (response MessageResponse, err error) |
|||
SendImage(c *fiber.Ctx, request ImageRequest) (response ImageResponse, err error) |
|||
SendFile(c *fiber.Ctx, request FileRequest) (response FileResponse, err error) |
|||
SendVideo(c *fiber.Ctx, request VideoRequest) (response VideoResponse, err error) |
|||
SendContact(c *fiber.Ctx, request ContactRequest) (response ContactResponse, err error) |
|||
} |
|||
@ -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"` |
|||
} |
|||
@ -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"` |
|||
} |
|||
@ -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"` |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"github.com/gofiber/fiber/v2" |
|||
) |
|||
|
|||
type IUserService interface { |
|||
Info(c *fiber.Ctx, request InfoRequest) (response InfoResponse, err error) |
|||
Avatar(c *fiber.Ctx, request AvatarRequest) (response AvatarResponse, err error) |
|||
MyListGroups(c *fiber.Ctx) (response MyListGroupsResponse, err error) |
|||
MyPrivacySetting(c *fiber.Ctx) (response MyPrivacySettingResponse, err error) |
|||
} |
|||
@ -1,12 +1,118 @@ |
|||
package services |
|||
|
|||
import ( |
|||
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs" |
|||
"context" |
|||
"errors" |
|||
"fmt" |
|||
"github.com/aldinokemal/go-whatsapp-web-multidevice/config" |
|||
domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" |
|||
"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 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 |
|||
} |
|||
|
|||
func NewAppService(waCli *whatsmeow.Client) domainApp.IAppService { |
|||
return &serviceApp{ |
|||
WaCli: waCli, |
|||
} |
|||
} |
|||
|
|||
func (service serviceApp) Login(c *fiber.Ctx) (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(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 serviceApp) Reconnect(c *fiber.Ctx) (err error) { |
|||
service.WaCli.Disconnect() |
|||
return service.WaCli.Connect() |
|||
} |
|||
@ -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() |
|||
} |
|||
@ -1,14 +1,310 @@ |
|||
package services |
|||
|
|||
import ( |
|||
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs" |
|||
"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" |
|||
"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 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(c *fiber.Ctx, 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(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 serviceSend) SendImage(c *fiber.Ctx, 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 = 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 serviceSend) SendFile(c *fiber.Ctx, request domainSend.FileRequest) (response domainSend.FileResponse, 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 serviceSend) SendVideo(c *fiber.Ctx, 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 = 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 serviceSend) SendContact(c *fiber.Ctx, 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(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 |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
package services |
|||
|
|||
import ( |
|||
"github.com/aldinokemal/go-whatsapp-web-multidevice/structs" |
|||
"github.com/gofiber/fiber/v2" |
|||
) |
|||
|
|||
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) |
|||
} |
|||
@ -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"` |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue