Browse Source
feat: introduce domain (#28)
feat: introduce domain (#28)
* feat: introduce domain * fix: context parsing * feat: add fetch all connected devices * feat: move controller to transport layer rest * feat: change struct service name * feat: add middleware JID * feat: remove unsed packagepull/31/head v3.9.0
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 898 additions and 833 deletions
-
31src/cmd/root.go
-
8src/config/settings.go
-
58src/controllers/app_controller.go
-
12src/domains/app/app.go
-
6src/domains/app/devices.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
-
2src/go.mod
-
6src/go.sum
-
69src/internal/rest/app_rest.go
-
66src/internal/rest/send_controller.go
-
41src/internal/rest/user_controller.go
-
21src/middleware/select_jid.go
-
145src/services/app_service.go
-
118src/services/app_service_impl.go
-
312src/services/send_service.go
-
310src/services/send_service_impl.go
-
115src/services/user_service.go
-
114src/services/user_service_impl.go
-
67src/structs/send_struct.go
-
48src/structs/user_struct.go
-
2src/utils/whatsapp.go
-
12src/validations/send_validation.go
-
6src/validations/send_validation_test.go
-
6src/validations/user_validation.go
-
4src/views/index.html
@ -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, |
|||
}) |
|||
} |
|||
@ -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) |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
package app |
|||
|
|||
type FetchDevicesResponse struct { |
|||
Name string |
|||
Device string |
|||
} |
|||
@ -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 ( |
|||
"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) |
|||
} |
|||
@ -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 ( |
|||
"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) |
|||
} |
|||
@ -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, |
|||
}) |
|||
} |
|||
@ -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() |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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" |
|||
"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 |
|||
} |
|||
@ -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 +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 |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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"` |
|||
} |
|||
@ -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"` |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue