mirror of
https://github.com/Neur0toxine/waba-coreapp-mock.git
synced 2024-11-22 04:56:10 +03:00
more configurability
This commit is contained in:
parent
6825c32bcf
commit
b5bd0480b1
4
main.go
4
main.go
@ -1,7 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/mkideal/cli"
|
"github.com/mkideal/cli"
|
||||||
@ -22,6 +24,8 @@ func main() {
|
|||||||
gin.SetMode(gin.DebugMode)
|
gin.SetMode(gin.DebugMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
http.DefaultClient.Timeout = time.Second * 30
|
||||||
|
|
||||||
return NewServer().Run(argv.Address)
|
return NewServer().Run(argv.Address)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
98
models.go
98
models.go
@ -110,6 +110,104 @@ type Message struct {
|
|||||||
Interactive *MessageInteractive `json:"interactive,omitempty"`
|
Interactive *MessageInteractive `json:"interactive,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InboundMessage struct {
|
||||||
|
Message
|
||||||
|
Button *InboundMessageButton `json:"button,omitempty"`
|
||||||
|
Context *InboundMessageContext `json:"context,omitempty"`
|
||||||
|
From string `json:"from,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Identity *InboundMessageIdentity `json:"identity,omitempty"`
|
||||||
|
Timestamp string `json:"timestamp,omitempty"`
|
||||||
|
System *MessageSystem `json:"system,omitempty"`
|
||||||
|
Voice *MessageMedia `json:"voice,omitempty"`
|
||||||
|
Referral *Referral `json:"referral,omitempty"`
|
||||||
|
Errors []InboundError `json:"errors,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundWebhook struct {
|
||||||
|
Contacts []InboundContact `json:"contacts,omitempty"`
|
||||||
|
Messages []InboundMessage `json:"messages,omitempty"`
|
||||||
|
Statuses []InboundStatus `json:"statuses,omitempty"`
|
||||||
|
Errors []InboundError `json:"errors,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundContact struct {
|
||||||
|
Profile *Profile `json:"profile,omitempty"`
|
||||||
|
WaID string `json:"wa_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundMessageButton struct {
|
||||||
|
Payload string `json:"payload,omitempty"`
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundMessageIdentity struct {
|
||||||
|
Acknowledged string `json:"acknowledged,omitempty"`
|
||||||
|
CreatedTimestamp string `json:"created_timestamp,omitempty"`
|
||||||
|
Hash string `json:"hash,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundMessageContext struct {
|
||||||
|
From string `json:"from,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
GroupID string `json:"group_id,omitempty"`
|
||||||
|
Mentions []string `json:"mentions,omitempty"`
|
||||||
|
Forwarded bool `json:"forwarded,omitempty"`
|
||||||
|
FrequentlyForwarded bool `json:"frequently_forwarded,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundStatus struct {
|
||||||
|
Conversation *InboundStatusConversation `json:"conversation,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Pricing *InboundStatusPricing `json:"pricing,omitempty"`
|
||||||
|
RecipientID string `json:"recipient_id,omitempty"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
Timestamp json.Number `json:"timestamp,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundStatusPricing struct {
|
||||||
|
Billable bool `json:"billable,omitempty"`
|
||||||
|
PricingModel string `json:"pricing_model,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundStatusConversation struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Origin InboundStatusConversationOrigin `json:"origin,omitempty"`
|
||||||
|
ExpirationTimestamp json.Number `json:"expiration_timestamp,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundStatusConversationOrigin struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundError struct {
|
||||||
|
Code int `json:"code,omitempty"`
|
||||||
|
Details string `json:"details,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Referral struct {
|
||||||
|
Headline string `json:"headline,omitempty"`
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
SourceType string `json:"source_type,omitempty"`
|
||||||
|
SourceID string `json:"source_id,omitempty"`
|
||||||
|
SourceURL string `json:"source_url,omitempty"`
|
||||||
|
Image *MessageMedia `json:"image,omitempty"`
|
||||||
|
Video *MessageMedia `json:"video,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageSystem struct {
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
NewWaID string `json:"new_wa_id,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Identity string `json:"identity,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Profile struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type MessagesResponse struct {
|
type MessagesResponse struct {
|
||||||
BaseResponse
|
BaseResponse
|
||||||
Messages []IDModel `json:"messages,omitempty"`
|
Messages []IDModel `json:"messages,omitempty"`
|
||||||
|
98
server.go
98
server.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@ -15,17 +16,31 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Mock struct {
|
type Mock struct {
|
||||||
ContactsSuccess bool `json:"contacts_success"`
|
ContactsSuccess bool `json:"contacts_success"`
|
||||||
MessagesSuccess bool `json:"messages_success"`
|
MessagesSuccess bool `json:"messages_success"`
|
||||||
|
MessagesStatus string `json:"messages_success_status" validate:"oneof=sent read failed"`
|
||||||
|
Webhook string `json:"webhook" validate:"url,startswith=http"`
|
||||||
|
WebhookHeaders map[string]string `json:"webhook_headers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
g *gin.Engine
|
g *gin.Engine
|
||||||
mock Mock
|
shooter *Shooter
|
||||||
|
mock Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer() (s *Server) {
|
func NewServer() (s *Server) {
|
||||||
s = &Server{g: gin.New()}
|
s = &Server{
|
||||||
|
g: gin.New(),
|
||||||
|
mock: Mock{
|
||||||
|
ContactsSuccess: true,
|
||||||
|
MessagesSuccess: true,
|
||||||
|
MessagesStatus: "sent",
|
||||||
|
Webhook: "",
|
||||||
|
WebhookHeaders: map[string]string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s.updateShooter()
|
||||||
s.g.GET("/mock", s.mockData)
|
s.g.GET("/mock", s.mockData)
|
||||||
s.g.POST("/mock", s.updateMockData)
|
s.g.POST("/mock", s.updateMockData)
|
||||||
api := s.g.Group("/v1")
|
api := s.g.Group("/v1")
|
||||||
@ -40,6 +55,15 @@ func (s *Server) Run(addr ...string) error {
|
|||||||
return s.g.Run(addr...)
|
return s.g.Run(addr...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) updateShooter() {
|
||||||
|
if s.shooter == nil {
|
||||||
|
s.shooter = NewShooter(s.mock.Webhook, s.mock.WebhookHeaders)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.shooter.Webhook = s.mock.Webhook
|
||||||
|
s.shooter.Headers = s.mock.WebhookHeaders
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) baseResponseOk() BaseResponse {
|
func (s *Server) baseResponseOk() BaseResponse {
|
||||||
return BaseResponse{
|
return BaseResponse{
|
||||||
Meta: &Metadata{
|
Meta: &Metadata{
|
||||||
@ -68,11 +92,34 @@ func (s *Server) mockData(c *gin.Context) {
|
|||||||
|
|
||||||
func (s *Server) updateMockData(c *gin.Context) {
|
func (s *Server) updateMockData(c *gin.Context) {
|
||||||
var mock Mock
|
var mock Mock
|
||||||
if err := s.bindRequest(c, &mock); err != nil {
|
if err := c.ShouldBindJSON(&mock); err != nil {
|
||||||
c.AbortWithStatus(http.StatusBadRequest)
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.mock = mock
|
|
||||||
|
current := s.mock
|
||||||
|
current.ContactsSuccess = mock.ContactsSuccess
|
||||||
|
current.MessagesSuccess = mock.MessagesSuccess
|
||||||
|
|
||||||
|
if mock.MessagesStatus != "" {
|
||||||
|
current.MessagesStatus = mock.MessagesStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
if mock.Webhook != "" {
|
||||||
|
current.Webhook = mock.Webhook
|
||||||
|
}
|
||||||
|
|
||||||
|
if mock.WebhookHeaders != nil {
|
||||||
|
current.WebhookHeaders = mock.WebhookHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validate.Struct(current); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mock = current
|
||||||
|
s.updateShooter()
|
||||||
c.JSON(http.StatusOK, s.mock)
|
c.JSON(http.StatusOK, s.mock)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,12 +153,47 @@ func (s *Server) messagesHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
messageID := RandomString(27)
|
||||||
|
text := ""
|
||||||
|
if req.Text != nil {
|
||||||
|
text = req.Text.Body
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Received new message: %#v\n", req)
|
log.Printf("Received new message: %#v\n", req)
|
||||||
|
|
||||||
|
if s.mock.Webhook != "" {
|
||||||
|
defer func(msgID, text string, to string) {
|
||||||
|
go func(msgID, text string, to string) {
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
code, err := s.shooter.SendStatus(InboundStatus{
|
||||||
|
Type: "message",
|
||||||
|
ID: messageID,
|
||||||
|
RecipientID: to,
|
||||||
|
Status: s.mock.MessagesStatus,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("status webhook code: %d\n", code)
|
||||||
|
|
||||||
|
if text == "reply" {
|
||||||
|
code, err := s.shooter.SendText("Replying to the message", to)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("reply webhook code: %d\n", code)
|
||||||
|
}
|
||||||
|
}(msgID, text, req.To)
|
||||||
|
}(messageID, text, req.To)
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, MessagesResponse{
|
c.JSON(http.StatusOK, MessagesResponse{
|
||||||
BaseResponse: s.baseResponseOk(),
|
BaseResponse: s.baseResponseOk(),
|
||||||
Messages: []IDModel{{
|
Messages: []IDModel{{
|
||||||
ID: RandomString(27),
|
ID: messageID,
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
84
shooter.go
Normal file
84
shooter.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Shooter struct {
|
||||||
|
Webhook string
|
||||||
|
Headers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewShooter(webhook string, headers map[string]string) *Shooter {
|
||||||
|
return &Shooter{
|
||||||
|
Webhook: webhook,
|
||||||
|
Headers: headers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Shooter) makeRequest(v interface{}) (*http.Request, error) {
|
||||||
|
wh, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, s.Webhook, bytes.NewReader(wh))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for h, v := range s.Headers {
|
||||||
|
req.Header.Set(h, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Shooter) SendStatus(status InboundStatus) (int, error) {
|
||||||
|
req, err := s.makeRequest(InboundWebhook{
|
||||||
|
Statuses: []InboundStatus{status},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.StatusCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Shooter) SendText(text, from string) (int, error) {
|
||||||
|
req, err := s.makeRequest(InboundWebhook{
|
||||||
|
Contacts: []InboundContact{{
|
||||||
|
Profile: &Profile{
|
||||||
|
Name: from,
|
||||||
|
},
|
||||||
|
WaID: from,
|
||||||
|
}},
|
||||||
|
Messages: []InboundMessage{{
|
||||||
|
Message: Message{
|
||||||
|
Type: "text",
|
||||||
|
Text: &MessageText{
|
||||||
|
Body: text,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
From: from,
|
||||||
|
ID: RandomString(27),
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.StatusCode, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user