diff --git a/src/config/settings.go b/src/config/settings.go index c23500e..4589fed 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -6,7 +6,7 @@ import ( ) var ( - AppVersion = "v3.7.1" + AppVersion = "v3.10.0" AppPort = "3000" AppDebug = false AppOs = fmt.Sprintf("AldinoKemal") diff --git a/src/domains/send/contact.go b/src/domains/send/contact.go index d6aabe2..fdd275f 100644 --- a/src/domains/send/contact.go +++ b/src/domains/send/contact.go @@ -8,5 +8,6 @@ type ContactRequest struct { } type ContactResponse struct { - Status string `json:"status"` + MessageID string `json:"message_id"` + Status string `json:"status"` } diff --git a/src/domains/send/file.go b/src/domains/send/file.go index e05fa9b..574ad94 100644 --- a/src/domains/send/file.go +++ b/src/domains/send/file.go @@ -9,5 +9,7 @@ type FileRequest struct { } type FileResponse struct { - Status string `json:"status"` + MessageID string `json:"message_id"` + Status string `json:"status"` + Url string `json:"url"` } diff --git a/src/domains/send/image.go b/src/domains/send/image.go index 9c15672..becdbe1 100644 --- a/src/domains/send/image.go +++ b/src/domains/send/image.go @@ -12,5 +12,7 @@ type ImageRequest struct { } type ImageResponse struct { - Status string `json:"status"` + MessageID string `json:"message_id"` + Status string `json:"status"` + Url string `json:"url"` } diff --git a/src/domains/send/link.go b/src/domains/send/link.go new file mode 100644 index 0000000..83bb68e --- /dev/null +++ b/src/domains/send/link.go @@ -0,0 +1,13 @@ +package send + +type LinkRequest struct { + Phone string `json:"phone" form:"phone"` + Caption string `json:"caption"` + Link string `json:"link"` + Type Type `json:"type" form:"type"` +} + +type LinkResponse struct { + MessageID string `json:"message_id"` + Status string `json:"status"` +} diff --git a/src/domains/send/send.go b/src/domains/send/send.go index 8e6b8ef..06fca62 100644 --- a/src/domains/send/send.go +++ b/src/domains/send/send.go @@ -15,4 +15,5 @@ type ISendService interface { 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) + SendLink(ctx context.Context, request LinkRequest) (response LinkResponse, err error) } diff --git a/src/domains/send/text.go b/src/domains/send/text.go index 6f49354..f115fa4 100644 --- a/src/domains/send/text.go +++ b/src/domains/send/text.go @@ -7,5 +7,6 @@ type MessageRequest struct { } type MessageResponse struct { - Status string `json:"status"` + MessageID string `json:"message_id"` + Status string `json:"status"` } diff --git a/src/domains/send/video.go b/src/domains/send/video.go index 90cb64e..aee3e72 100644 --- a/src/domains/send/video.go +++ b/src/domains/send/video.go @@ -12,5 +12,7 @@ type VideoRequest struct { } type VideoResponse struct { - Status string `json:"status"` + MessageID string `json:"message_id"` + Status string `json:"status"` + Url string `json:"url"` } diff --git a/src/internal/rest/send_controller.go b/src/internal/rest/send_controller.go index 81a5c35..dee923a 100644 --- a/src/internal/rest/send_controller.go +++ b/src/internal/rest/send_controller.go @@ -18,6 +18,7 @@ func InitRestSend(app *fiber.App, service domainSend.ISendService) Send { app.Post("/send/file", rest.SendFile) app.Post("/send/video", rest.SendVideo) app.Post("/send/contact", rest.SendContact) + app.Post("/send/link", rest.SendLink) return rest } @@ -158,3 +159,27 @@ func (controller *Send) SendContact(c *fiber.Ctx) error { Results: response, }) } + +func (controller *Send) SendLink(c *fiber.Ctx) error { + var request domainSend.LinkRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + err = validations.ValidateSendLink(request) + utils.PanicIfNeeded(err) + + if request.Type == domainSend.TypeGroup { + request.Phone = request.Phone + "@g.us" + } else { + request.Phone = request.Phone + "@s.whatsapp.net" + } + + response, err := controller.Service.SendLink(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Code: 200, + Message: response.Status, + Results: response, + }) +} diff --git a/src/services/send_service.go b/src/services/send_service.go index d08456f..fab6e74 100644 --- a/src/services/send_service.go +++ b/src/services/send_service.go @@ -35,13 +35,16 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess if !ok { return response, errors.New("invalid JID " + request.Phone) } + + msgId := whatsmeow.GenerateMessageID() msg := &waProto.Message{Conversation: proto.String(request.Message)} - ts, err := service.WaCli.SendMessage(ctx, recipient, "", msg) + ts, err := service.WaCli.SendMessage(ctx, recipient, msgId, msg) if err != nil { return response, err - } else { - response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts) } + + response.MessageID = msgId + response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts) return response, nil } @@ -111,6 +114,7 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima } dataWaThumbnail, err := os.ReadFile(imageThumbnail) + msgId := whatsmeow.GenerateMessageID() msg := &waProto.Message{ImageMessage: &waProto.ImageMessage{ JpegThumbnail: dataWaThumbnail, Caption: proto.String(dataWaCaption), @@ -123,7 +127,7 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima FileLength: proto.Uint64(uint64(len(dataWaImage))), ViewOnce: proto.Bool(request.ViewOnce), }} - ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg) + ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg) go func() { errDelete := utils.RemoveFile(0, deletedItems...) if errDelete != nil { @@ -132,10 +136,12 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima }() if err != nil { return response, err - } else { - response.Status = fmt.Sprintf("Message sent to %s (server timestamp: %s)", request.Phone, ts) - return response, nil } + + response.MessageID = msgId + response.Url = uploadedImage.URL + 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) { @@ -162,6 +168,7 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File return response, err } + msgId := whatsmeow.GenerateMessageID() msg := &waProto.Message{DocumentMessage: &waProto.DocumentMessage{ Url: proto.String(uploadedFile.URL), Mimetype: proto.String(http.DetectContentType(dataWaFile)), @@ -173,7 +180,7 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File FileEncSha256: uploadedFile.FileEncSHA256, DirectPath: proto.String(uploadedFile.DirectPath), }} - ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg) + ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg) go func() { errDelete := utils.RemoveFile(0, oriFilePath) if errDelete != nil { @@ -182,10 +189,12 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File }() if err != nil { return response, err - } else { - response.Status = fmt.Sprintf("Document sent to %s (server timestamp: %s)", request.Phone, ts) - return response, nil } + + response.MessageID = msgId + response.Url = uploadedFile.URL + 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) { @@ -250,7 +259,7 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid if err != nil { return response, err } - uploadedFile, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo) + uploaded, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo) if err != nil { fmt.Printf("Failed to upload file: %v", err) return response, err @@ -260,19 +269,20 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid return response, err } + msgId := whatsmeow.GenerateMessageID() msg := &waProto.Message{VideoMessage: &waProto.VideoMessage{ - Url: proto.String(uploadedFile.URL), + Url: proto.String(uploaded.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), + FileLength: proto.Uint64(uploaded.FileLength), + FileSha256: uploaded.FileSHA256, + FileEncSha256: uploaded.FileEncSHA256, + MediaKey: uploaded.MediaKey, + DirectPath: proto.String(uploaded.DirectPath), ViewOnce: proto.Bool(request.ViewOnce), JpegThumbnail: dataWaThumbnail, }} - ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg) + ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg) go func() { errDelete := utils.RemoveFile(0, deletedItems...) if errDelete != nil { @@ -281,10 +291,12 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid }() if err != nil { return response, err - } else { - response.Status = fmt.Sprintf("Video sent to %s (server timestamp: %s)", request.Phone, ts) - return response, nil } + + response.MessageID = msgId + response.Url = uploaded.URL + 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) { @@ -296,6 +308,7 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C } 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) + msgId := whatsmeow.GenerateMessageID() msg := &waProto.Message{ContactMessage: &waProto.ContactMessage{ DisplayName: proto.String(request.ContactName), Vcard: proto.String(msgVCard), @@ -303,8 +316,39 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C 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) } + + response.MessageID = msgId + response.Status = fmt.Sprintf("Contact sent to %s (server timestamp: %s)", request.Phone, ts) + return response, nil +} + +func (service serviceSend) SendLink(ctx context.Context, request domainSend.LinkRequest) (response domainSend.LinkResponse, err error) { + utils.MustLogin(service.WaCli) + + recipient, ok := utils.ParseJID(request.Phone) + if !ok { + return response, errors.New("invalid JID " + request.Phone) + } + + msgId := whatsmeow.GenerateMessageID() + msg := &waProto.Message{ExtendedTextMessage: &waProto.ExtendedTextMessage{ + Text: proto.String(request.Caption), + MatchedText: proto.String(request.Caption), + CanonicalUrl: proto.String(request.Link), + ContextInfo: &waProto.ContextInfo{ + ActionLink: &waProto.ActionLink{ + Url: proto.String(request.Link), + ButtonTitle: proto.String(request.Caption), + }, + }, + }} + ts, err := service.WaCli.SendMessage(ctx, recipient, msgId, msg) + if err != nil { + return response, err + } + + response.MessageID = msgId + response.Status = fmt.Sprintf("Link sent to %s (server timestamp: %s)", request.Phone, ts) return response, nil } diff --git a/src/validations/send_validation.go b/src/validations/send_validation.go index 9474232..b4e30ae 100644 --- a/src/validations/send_validation.go +++ b/src/validations/send_validation.go @@ -113,3 +113,19 @@ func ValidateSendContact(request domainSend.ContactRequest) { }) } } + +func ValidateSendLink(request domainSend.LinkRequest) error { + err := validation.ValidateStruct(&request, + validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)), + validation.Field(&request.Link, validation.Required), + validation.Field(&request.Caption, validation.Required), + ) + + if err != nil { + return utils.ValidationError{ + Message: err.Error(), + } + } + + return nil +}