Browse Source
feat: add reaction (#60)
feat: add reaction (#60)
* feat: add reaction * feat: separate message new domain * feat: update open api * feat: update UI * feat: update go modulespull/61/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 496 additions and 201 deletions
-
77docs/openapi.yaml
-
2src/cmd/root.go
-
2src/config/settings.go
-
9src/domains/message/message.go
-
12src/domains/message/reaction.go
-
11src/domains/message/revoke.go
-
12src/domains/message/update.go
-
10src/domains/send/message.go
-
2src/domains/send/send.go
-
11src/go.mod
-
59src/go.sum
-
78src/internal/rest/message.go
-
40src/internal/rest/send.go
-
95src/services/message.go
-
42src/services/send.go
-
50src/validations/message_validation.go
-
27src/validations/send_validation.go
-
156src/views/index.html
@ -0,0 +1,9 @@ |
|||||
|
package message |
||||
|
|
||||
|
import "context" |
||||
|
|
||||
|
type IMessageService interface { |
||||
|
ReactMessage(ctx context.Context, request ReactionRequest) (response ReactionResponse, err error) |
||||
|
RevokeMessage(ctx context.Context, request RevokeRequest) (response RevokeResponse, err error) |
||||
|
UpdateMessage(ctx context.Context, request UpdateMessageRequest) (response UpdateMessageResponse, err error) |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
package message |
||||
|
|
||||
|
type ReactionRequest struct { |
||||
|
MessageID string `json:"message_id" form:"message_id"` |
||||
|
Phone string `json:"phone" form:"phone"` |
||||
|
Emoji string `json:"emoji" form:"emoji"` |
||||
|
} |
||||
|
|
||||
|
type ReactionResponse struct { |
||||
|
MessageID string `json:"message_id"` |
||||
|
Status string `json:"status"` |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
package message |
||||
|
|
||||
|
type RevokeRequest struct { |
||||
|
MessageID string `json:"message_id" uri:"message_id"` |
||||
|
Phone string `json:"phone" form:"phone"` |
||||
|
} |
||||
|
|
||||
|
type RevokeResponse struct { |
||||
|
MessageID string `json:"message_id"` |
||||
|
Status string `json:"status"` |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
package message |
||||
|
|
||||
|
type UpdateMessageRequest struct { |
||||
|
MessageID string `json:"message_id" uri:"message_id"` |
||||
|
Message string `json:"message" form:"message"` |
||||
|
Phone string `json:"phone" form:"phone"` |
||||
|
} |
||||
|
|
||||
|
type UpdateMessageResponse struct { |
||||
|
MessageID string `json:"message_id"` |
||||
|
Status string `json:"status"` |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
package rest |
||||
|
|
||||
|
import ( |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" |
||||
|
domainMessage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" |
||||
|
"github.com/gofiber/fiber/v2" |
||||
|
) |
||||
|
|
||||
|
type Message struct { |
||||
|
Service domainMessage.IMessageService |
||||
|
} |
||||
|
|
||||
|
func InitRestMessage(app *fiber.App, service domainMessage.IMessageService) Message { |
||||
|
rest := Message{Service: service} |
||||
|
app.Post("/message/:message_id/reaction", rest.ReactMessage) |
||||
|
app.Post("/message/:message_id/revoke", rest.RevokeMessage) |
||||
|
app.Post("/message/:message_id/update", rest.UpdateMessage) |
||||
|
return rest |
||||
|
} |
||||
|
|
||||
|
func (controller *Message) RevokeMessage(c *fiber.Ctx) error { |
||||
|
var request domainMessage.RevokeRequest |
||||
|
err := c.BodyParser(&request) |
||||
|
utils.PanicIfNeeded(err) |
||||
|
|
||||
|
request.MessageID = c.Params("message_id") |
||||
|
whatsapp.SanitizePhone(&request.Phone) |
||||
|
|
||||
|
response, err := controller.Service.RevokeMessage(c.UserContext(), request) |
||||
|
utils.PanicIfNeeded(err) |
||||
|
|
||||
|
return c.JSON(utils.ResponseData{ |
||||
|
Status: 200, |
||||
|
Code: "SUCCESS", |
||||
|
Message: response.Status, |
||||
|
Results: response, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
func (controller *Message) UpdateMessage(c *fiber.Ctx) error { |
||||
|
var request domainMessage.UpdateMessageRequest |
||||
|
err := c.BodyParser(&request) |
||||
|
utils.PanicIfNeeded(err) |
||||
|
|
||||
|
request.MessageID = c.Params("message_id") |
||||
|
whatsapp.SanitizePhone(&request.Phone) |
||||
|
|
||||
|
response, err := controller.Service.UpdateMessage(c.UserContext(), request) |
||||
|
utils.PanicIfNeeded(err) |
||||
|
|
||||
|
return c.JSON(utils.ResponseData{ |
||||
|
Status: 200, |
||||
|
Code: "SUCCESS", |
||||
|
Message: response.Status, |
||||
|
Results: response, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
func (controller *Message) ReactMessage(c *fiber.Ctx) error { |
||||
|
var request message.ReactionRequest |
||||
|
err := c.BodyParser(&request) |
||||
|
utils.PanicIfNeeded(err) |
||||
|
|
||||
|
request.MessageID = c.Params("message_id") |
||||
|
whatsapp.SanitizePhone(&request.Phone) |
||||
|
|
||||
|
response, err := controller.Service.ReactMessage(c.UserContext(), request) |
||||
|
utils.PanicIfNeeded(err) |
||||
|
|
||||
|
return c.JSON(utils.ResponseData{ |
||||
|
Status: 200, |
||||
|
Code: "SUCCESS", |
||||
|
Message: response.Status, |
||||
|
Results: response, |
||||
|
}) |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
package services |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"fmt" |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" |
||||
|
domainMessage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/validations" |
||||
|
"go.mau.fi/whatsmeow" |
||||
|
waProto "go.mau.fi/whatsmeow/binary/proto" |
||||
|
"go.mau.fi/whatsmeow/types" |
||||
|
"google.golang.org/protobuf/proto" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
type serviceMessage struct { |
||||
|
WaCli *whatsmeow.Client |
||||
|
} |
||||
|
|
||||
|
func NewMessageService(waCli *whatsmeow.Client) domainMessage.IMessageService { |
||||
|
return &serviceMessage{ |
||||
|
WaCli: waCli, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (service serviceMessage) ReactMessage(ctx context.Context, request message.ReactionRequest) (response message.ReactionResponse, err error) { |
||||
|
if err = validations.ValidateReactMessage(ctx, request); err != nil { |
||||
|
return response, err |
||||
|
} |
||||
|
dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) |
||||
|
if err != nil { |
||||
|
return response, err |
||||
|
} |
||||
|
|
||||
|
msg := &waProto.Message{ |
||||
|
ReactionMessage: &waProto.ReactionMessage{ |
||||
|
Key: &waProto.MessageKey{ |
||||
|
FromMe: proto.Bool(true), |
||||
|
Id: proto.String(request.MessageID), |
||||
|
RemoteJid: proto.String(dataWaRecipient.String()), |
||||
|
}, |
||||
|
Text: proto.String(request.Emoji), |
||||
|
SenderTimestampMs: proto.Int64(time.Now().UnixMilli()), |
||||
|
}, |
||||
|
} |
||||
|
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg) |
||||
|
if err != nil { |
||||
|
return response, err |
||||
|
} |
||||
|
|
||||
|
response.MessageID = ts.ID |
||||
|
response.Status = fmt.Sprintf("Reaction sent to %s (server timestamp: %s)", request.Phone, ts) |
||||
|
return response, nil |
||||
|
} |
||||
|
|
||||
|
func (service serviceMessage) RevokeMessage(ctx context.Context, request domainMessage.RevokeRequest) (response domainMessage.RevokeResponse, err error) { |
||||
|
if err = validations.ValidateRevokeMessage(ctx, request); err != nil { |
||||
|
return response, err |
||||
|
} |
||||
|
dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) |
||||
|
if err != nil { |
||||
|
return response, err |
||||
|
} |
||||
|
|
||||
|
ts, err := service.WaCli.SendMessage(context.Background(), dataWaRecipient, service.WaCli.BuildRevoke(dataWaRecipient, types.EmptyJID, request.MessageID)) |
||||
|
if err != nil { |
||||
|
return response, err |
||||
|
} |
||||
|
|
||||
|
response.MessageID = ts.ID |
||||
|
response.Status = fmt.Sprintf("Revoke success %s (server timestamp: %s)", request.Phone, ts) |
||||
|
return response, nil |
||||
|
} |
||||
|
|
||||
|
func (service serviceMessage) UpdateMessage(ctx context.Context, request domainMessage.UpdateMessageRequest) (response domainMessage.UpdateMessageResponse, err error) { |
||||
|
if err = validations.ValidateUpdateMessage(ctx, request); err != nil { |
||||
|
return response, err |
||||
|
} |
||||
|
|
||||
|
dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) |
||||
|
if err != nil { |
||||
|
return response, err |
||||
|
} |
||||
|
|
||||
|
msg := &waProto.Message{Conversation: proto.String(request.Message)} |
||||
|
ts, err := service.WaCli.SendMessage(context.Background(), dataWaRecipient, service.WaCli.BuildEdit(dataWaRecipient, request.MessageID, msg)) |
||||
|
if err != nil { |
||||
|
return response, err |
||||
|
} |
||||
|
|
||||
|
response.MessageID = ts.ID |
||||
|
response.Status = fmt.Sprintf("Update message success %s (server timestamp: %s)", request.Phone, ts) |
||||
|
return response, nil |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
package validations |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" |
||||
|
domainMessage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" |
||||
|
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" |
||||
|
validation "github.com/go-ozzo/ozzo-validation/v4" |
||||
|
) |
||||
|
|
||||
|
func ValidateRevokeMessage(ctx context.Context, request domainMessage.RevokeRequest) error { |
||||
|
err := validation.ValidateStructWithContext(ctx, &request, |
||||
|
validation.Field(&request.Phone, validation.Required), |
||||
|
validation.Field(&request.MessageID, validation.Required), |
||||
|
) |
||||
|
|
||||
|
if err != nil { |
||||
|
return pkgError.ValidationError(err.Error()) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func ValidateUpdateMessage(ctx context.Context, request domainMessage.UpdateMessageRequest) error { |
||||
|
err := validation.ValidateStructWithContext(ctx, &request, |
||||
|
validation.Field(&request.Phone, validation.Required), |
||||
|
validation.Field(&request.MessageID, validation.Required), |
||||
|
validation.Field(&request.Message, validation.Required), |
||||
|
) |
||||
|
|
||||
|
if err != nil { |
||||
|
return pkgError.ValidationError(err.Error()) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func ValidateReactMessage(ctx context.Context, request message.ReactionRequest) error { |
||||
|
err := validation.ValidateStructWithContext(ctx, &request, |
||||
|
validation.Field(&request.Phone, validation.Required), |
||||
|
validation.Field(&request.MessageID, validation.Required), |
||||
|
validation.Field(&request.Emoji, validation.Required), |
||||
|
) |
||||
|
|
||||
|
if err != nil { |
||||
|
return pkgError.ValidationError(err.Error()) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue