This commit is contained in:
2026-06-09 18:03:16 +08:00
parent f45fbfd5f0
commit ac7f351308
29 changed files with 4867 additions and 254 deletions
+250 -48
View File
@@ -1,6 +1,8 @@
package widget
import (
"context"
"errors"
"net/http"
"strconv"
"strings"
@@ -89,6 +91,14 @@ func (h *WidgetHandler) Config(c *gin.Context) {
resp, err := h.widgetService.Init(c.Request.Context(), req)
if err != nil {
if widgetWebsiteTokenNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
if strings.Contains(err.Error(), "Account is suspended") {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Account is suspended"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@@ -140,6 +150,10 @@ func (h *WidgetHandler) SendMessage(c *gin.Context) {
req.WidgetToken = widgetToken
resp, err := h.widgetService.SendMessage(c.Request.Context(), req)
if err != nil {
if errors.Is(err, service.ErrWidgetMessageContentTooLong) && c.FullPath() != "/widget/messages" {
c.JSON(http.StatusUnprocessableEntity, gin.H{"message": err.Error()})
return
}
status := http.StatusBadRequest
if err.Error() == "invalid widget_token" || err.Error() == "widget_token required" {
status = http.StatusUnauthorized
@@ -205,13 +219,10 @@ func (h *WidgetHandler) GetLatestMessages(c *gin.Context) {
return
}
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "25"))
if limit <= 0 || limit > 100 {
limit = 25
}
after, _ := strconv.ParseUint(c.DefaultQuery("after", "0"), 10, 64)
before, _ := strconv.ParseUint(c.DefaultQuery("before", "0"), 10, 64)
messages, total, conversation, err := h.widgetService.GetLatestConversationMessages(c.Request.Context(), widgetToken, offset, limit)
messages, total, conversation, err := h.widgetService.GetLatestConversationMessages(c.Request.Context(), widgetToken, uint(after), uint(before))
if err != nil {
status := http.StatusBadRequest
if err.Error() == "invalid widget_token" {
@@ -229,7 +240,7 @@ func (h *WidgetHandler) GetLatestMessages(c *gin.Context) {
}
payload = append(payload, messagePayload)
}
meta := gin.H{"total": total, "offset": offset, "limit": limit}
meta := gin.H{"total": total}
if conversation != nil && conversation.ContactLastSeenAt != nil {
meta["contact_last_seen_at"] = *conversation.ContactLastSeenAt
}
@@ -378,6 +389,10 @@ func (h *WidgetHandler) GetCableToken(c *gin.Context) {
func (h *WidgetHandler) UpdateContact(c *gin.Context) {
widgetToken := widgetTokenFromRequest(c)
if widgetToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "widget_token required"})
return
}
@@ -406,6 +421,10 @@ func (h *WidgetHandler) UpdateContact(c *gin.Context) {
AdditionalAttributes: req.AdditionalAttributes,
})
if err != nil {
if isChatwootWidgetRoute(c) && widgetConversationNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
status := http.StatusBadRequest
if err.Error() == "invalid widget_token" {
status = http.StatusUnauthorized
@@ -421,12 +440,20 @@ func (h *WidgetHandler) UpdateContact(c *gin.Context) {
func (h *WidgetHandler) GetContact(c *gin.Context) {
widgetToken := widgetTokenFromRequest(c)
if widgetToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "widget_token required"})
return
}
contact, err := h.widgetService.GetContact(c.Request.Context(), widgetToken)
if err != nil {
if isChatwootWidgetRoute(c) && widgetConversationNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
status := http.StatusBadRequest
if err.Error() == "invalid widget_token" {
status = http.StatusUnauthorized
@@ -440,6 +467,10 @@ func (h *WidgetHandler) GetContact(c *gin.Context) {
func (h *WidgetHandler) DestroyContactCustomAttributes(c *gin.Context) {
widgetToken := widgetTokenFromRequest(c)
if widgetToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "widget_token required"})
return
}
@@ -453,6 +484,10 @@ func (h *WidgetHandler) DestroyContactCustomAttributes(c *gin.Context) {
}
contact, err := h.widgetService.DeleteContactCustomAttributes(c.Request.Context(), widgetToken, req.CustomAttributes)
if err != nil {
if isChatwootWidgetRoute(c) && widgetConversationNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@@ -558,21 +593,29 @@ func (h *WidgetHandler) ToggleTyping(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
c.Status(http.StatusOK)
}
func (h *WidgetHandler) UpdateLastSeen(c *gin.Context) {
widgetToken := widgetTokenFromRequest(c)
if widgetToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "widget_token required"})
return
}
conversation, err := h.widgetService.UpdateLastSeen(c.Request.Context(), widgetToken)
_, err := h.widgetService.UpdateLastSeen(c.Request.Context(), widgetToken)
if err != nil {
if isChatwootWidgetRoute(c) && widgetConversationNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"contact_last_seen_at": conversation.ContactLastSeenAt})
c.Status(http.StatusOK)
}
func (h *WidgetHandler) ToggleStatus(c *gin.Context) {
@@ -581,17 +624,29 @@ func (h *WidgetHandler) ToggleStatus(c *gin.Context) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "widget_token required"})
return
}
conversation, err := h.widgetService.ResolveLatestConversation(c.Request.Context(), widgetToken)
_, err := h.widgetService.ResolveLatestConversation(c.Request.Context(), widgetToken)
if err != nil {
if errors.Is(err, service.ErrWidgetEndConversationDisabled) {
c.Status(http.StatusForbidden)
return
}
if errors.Is(err, service.ErrWidgetConversationNotFound) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, widgetConversationPayload(*conversation))
c.Status(http.StatusOK)
}
func (h *WidgetHandler) SetConversationCustomAttributes(c *gin.Context) {
widgetToken := widgetTokenFromRequest(c)
if widgetToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "widget_token required"})
return
}
@@ -602,17 +657,25 @@ func (h *WidgetHandler) SetConversationCustomAttributes(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body", "details": err.Error()})
return
}
conversation, err := h.widgetService.SetLatestConversationCustomAttributes(c.Request.Context(), widgetToken, req.CustomAttributes)
_, err := h.widgetService.SetLatestConversationCustomAttributes(c.Request.Context(), widgetToken, req.CustomAttributes)
if err != nil {
if isChatwootWidgetRoute(c) && widgetConversationNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, conversation)
c.Status(http.StatusOK)
}
func (h *WidgetHandler) DestroyConversationCustomAttributes(c *gin.Context) {
widgetToken := widgetTokenFromRequest(c)
if widgetToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "widget_token required"})
return
}
@@ -625,6 +688,10 @@ func (h *WidgetHandler) DestroyConversationCustomAttributes(c *gin.Context) {
}
conversation, err := h.widgetService.DeleteLatestConversationCustomAttributes(c.Request.Context(), widgetToken, req.CustomAttribute)
if err != nil {
if isChatwootWidgetRoute(c) && widgetConversationNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@@ -634,11 +701,19 @@ func (h *WidgetHandler) DestroyConversationCustomAttributes(c *gin.Context) {
func (h *WidgetHandler) ListInboxMembers(c *gin.Context) {
websiteToken := c.Query("website_token")
if websiteToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": "website_token is required"})
return
}
members, err := h.widgetService.GetInboxMembersByWebsiteToken(c.Request.Context(), websiteToken)
if err != nil {
if isChatwootWidgetRoute(c) && widgetWebsiteTokenNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@@ -648,11 +723,19 @@ func (h *WidgetHandler) ListInboxMembers(c *gin.Context) {
func (h *WidgetHandler) ListCampaigns(c *gin.Context) {
websiteToken := c.Query("website_token")
if websiteToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": "website_token is required"})
return
}
campaigns, err := h.widgetService.GetCampaignsByWebsiteToken(c.Request.Context(), websiteToken)
if err != nil {
if isChatwootWidgetRoute(c) && widgetWebsiteTokenNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@@ -660,16 +743,28 @@ func (h *WidgetHandler) ListCampaigns(c *gin.Context) {
}
func (h *WidgetHandler) CreateEvent(c *gin.Context) {
websiteToken := c.Query("website_token")
var req struct {
Name string `json:"name"`
EventInfo map[string]any `json:"event_info"`
Name string `json:"name" form:"name"`
WebsiteToken string `json:"website_token" form:"website_token"`
EventInfo map[string]any `json:"event_info" form:"event_info"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body", "details": err.Error()})
return
}
websiteToken := strings.TrimSpace(c.Query("website_token"))
if websiteToken == "" {
websiteToken = strings.TrimSpace(req.WebsiteToken)
}
if err := h.widgetService.TrackEvent(c.Request.Context(), websiteToken, widgetTokenFromRequest(c), req.Name, req.EventInfo); err != nil {
if isChatwootWidgetRoute(c) && widgetWebsiteTokenNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
if isChatwootWidgetRoute(c) && widgetConversationNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@@ -679,6 +774,10 @@ func (h *WidgetHandler) CreateEvent(c *gin.Context) {
func (h *WidgetHandler) AddLabel(c *gin.Context) {
widgetToken := widgetTokenFromRequest(c)
if widgetToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "widget_token required"})
return
}
@@ -690,6 +789,10 @@ func (h *WidgetHandler) AddLabel(c *gin.Context) {
return
}
if err := h.widgetService.AddLabelToLatestConversation(c.Request.Context(), widgetToken, req.Label); err != nil {
if isChatwootWidgetRoute(c) && widgetConversationNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@@ -699,10 +802,18 @@ func (h *WidgetHandler) AddLabel(c *gin.Context) {
func (h *WidgetHandler) RemoveLabel(c *gin.Context) {
widgetToken := widgetTokenFromRequest(c)
if widgetToken == "" {
if isChatwootWidgetRoute(c) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "widget_token required"})
return
}
if err := h.widgetService.RemoveLabelFromLatestConversation(c.Request.Context(), widgetToken, c.Param("label_id")); err != nil {
if isChatwootWidgetRoute(c) && widgetConversationNotFoundLike(err) {
c.Status(http.StatusNotFound)
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@@ -716,11 +827,19 @@ func (h *WidgetHandler) SendTranscript(c *gin.Context) {
return
}
if err := h.widgetService.SendTranscript(c.Request.Context(), widgetToken); err != nil {
status := widgetErrorStatus(err)
if strings.Contains(err.Error(), "conversation not found") {
status = http.StatusTooManyRequests
if errors.Is(err, service.ErrWidgetConversationNotFound) {
c.Status(http.StatusTooManyRequests)
return
}
c.JSON(status, gin.H{"error": err.Error()})
if errors.Is(err, service.ErrEmailTranscriptDisabled) {
c.Status(http.StatusPaymentRequired)
return
}
if errors.Is(err, service.ErrEmailRateLimited) {
c.Status(http.StatusTooManyRequests)
return
}
c.JSON(widgetErrorStatus(err), gin.H{"error": err.Error()})
return
}
c.Status(http.StatusOK)
@@ -734,10 +853,10 @@ func (h *WidgetHandler) AddDyteParticipantToMeeting(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body", "details": err.Error()})
return
}
resp, err := h.widgetService.AddDyteParticipant(c.Request.Context(), c.Query("website_token"), req.MessageID)
resp, err := h.widgetService.AddDyteParticipant(c.Request.Context(), c.Query("website_token"), widgetTokenFromRequest(c), req.MessageID)
if err != nil {
status := http.StatusUnprocessableEntity
if strings.Contains(err.Error(), "website_token") {
if strings.Contains(err.Error(), "website_token") || strings.Contains(err.Error(), "widget_token") {
status = http.StatusBadRequest
}
c.JSON(status, gin.H{"error": err.Error()})
@@ -803,7 +922,12 @@ func (h *WidgetHandler) PublicListConversations(c *gin.Context) {
}
payload := make([]gin.H, 0, len(conversations))
for _, conversation := range conversations {
payload = append(payload, publicConversationPayload(conversation, nil))
messages, _, _, err := h.widgetService.PublicListMessages(c.Request.Context(), c.Param("inbox_id"), c.Param("contact_id"), publicDisplayID(conversation), service.PublicMessageListOptions{})
if err != nil {
c.JSON(widgetErrorStatus(err), gin.H{"error": err.Error()})
return
}
payload = append(payload, h.publicConversationPayload(c.Request.Context(), conversation, messages))
}
c.JSON(http.StatusOK, payload)
}
@@ -821,7 +945,7 @@ func (h *WidgetHandler) PublicCreateConversation(c *gin.Context) {
c.JSON(widgetErrorStatus(err), gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, publicConversationPayload(*conversation, nil))
c.JSON(http.StatusOK, h.publicConversationPayload(c.Request.Context(), *conversation, nil))
}
func (h *WidgetHandler) PublicGetConversation(c *gin.Context) {
@@ -834,8 +958,8 @@ func (h *WidgetHandler) PublicGetConversation(c *gin.Context) {
c.JSON(widgetErrorStatus(err), gin.H{"error": err.Error()})
return
}
messages, _, _, _ := h.widgetService.PublicListMessages(c.Request.Context(), c.Param("inbox_id"), c.Param("contact_id"), conversationID, 0, 100)
c.JSON(http.StatusOK, publicConversationPayload(*conversation, messages))
messages, _, _, _ := h.widgetService.PublicListMessages(c.Request.Context(), c.Param("inbox_id"), c.Param("contact_id"), conversationID, service.PublicMessageListOptions{})
c.JSON(http.StatusOK, h.publicConversationPayload(c.Request.Context(), *conversation, messages))
}
func (h *WidgetHandler) PublicToggleStatus(c *gin.Context) {
@@ -848,7 +972,7 @@ func (h *WidgetHandler) PublicToggleStatus(c *gin.Context) {
c.JSON(widgetErrorStatus(err), gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, publicConversationPayload(*conversation, nil))
c.JSON(http.StatusOK, h.publicConversationPayload(c.Request.Context(), *conversation, nil))
}
func (h *WidgetHandler) PublicToggleTyping(c *gin.Context) {
@@ -889,15 +1013,20 @@ func (h *WidgetHandler) PublicListMessages(c *gin.Context) {
return
}
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "25"))
messages, _, conversation, err := h.widgetService.PublicListMessages(c.Request.Context(), c.Param("inbox_id"), c.Param("contact_id"), conversationID, offset, limit)
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "0"))
before, _ := strconv.ParseUint(c.Query("before"), 10, 64)
messages, _, conversation, err := h.widgetService.PublicListMessages(c.Request.Context(), c.Param("inbox_id"), c.Param("contact_id"), conversationID, service.PublicMessageListOptions{Before: uint(before), Offset: offset, Limit: limit})
if err != nil {
c.JSON(widgetErrorStatus(err), gin.H{"error": err.Error()})
return
}
payload := make([]gin.H, 0, len(messages))
for _, message := range messages {
payload = append(payload, publicMessagePayload(message, *conversation))
messagePayload := publicMessagePayload(message, *conversation)
if attachments, err := h.widgetService.GetMessageAttachments(c.Request.Context(), message.ID); err == nil && len(attachments) > 0 {
messagePayload["attachments"] = widgetAttachmentPayloads(attachments)
}
payload = append(payload, messagePayload)
}
c.JSON(http.StatusOK, payload)
}
@@ -912,12 +1041,16 @@ func (h *WidgetHandler) PublicCreateMessage(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body", "details": err.Error()})
return
}
message, conversation, err := h.widgetService.PublicCreateMessage(c.Request.Context(), c.Param("inbox_id"), c.Param("contact_id"), conversationID, req)
message, conversation, attachments, err := h.widgetService.PublicCreateMessage(c.Request.Context(), c.Param("inbox_id"), c.Param("contact_id"), conversationID, req)
if err != nil {
c.JSON(widgetErrorStatus(err), gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, publicMessagePayload(*message, *conversation))
payload := publicMessagePayload(*message, *conversation)
if len(attachments) > 0 {
payload["attachments"] = widgetAttachmentPayloads(attachments)
}
c.JSON(http.StatusOK, payload)
}
func (h *WidgetHandler) PublicUpdateMessage(c *gin.Context) {
@@ -1013,26 +1146,51 @@ func bindWidgetSendMessageRequest(c *gin.Context) (service.WidgetSendMessageRequ
conversationID = &value
}
}
var replyTo *uint
if rawID := firstFormValue(form.Value, "reply_to", "message[reply_to]"); rawID != "" {
if id, err := strconv.ParseUint(rawID, 10, 64); err == nil && id > 0 {
value := uint(id)
replyTo = &value
}
}
attachments := form.Value["message[attachments][]"]
if len(attachments) == 0 {
attachments = form.Value["attachments[]"]
}
customAttributes := map[string]any{}
for key, values := range form.Value {
if strings.HasPrefix(key, "custom_attributes[") && strings.HasSuffix(key, "]") && len(values) > 0 {
attrKey := strings.TrimSuffix(strings.TrimPrefix(key, "custom_attributes["), "]")
if attrKey != "" {
customAttributes[attrKey] = values[0]
}
}
}
if len(customAttributes) == 0 {
customAttributes = nil
}
return service.WidgetSendMessageRequest{
Content: content,
ContentType: contentType,
ConversationID: conversationID,
AttachmentIDs: attachments,
Content: content,
ContentType: contentType,
ConversationID: conversationID,
AttachmentIDs: attachments,
CustomAttributes: customAttributes,
Labels: form.Value["labels[]"],
ReplyTo: replyTo,
}, nil
}
var body struct {
Content string `json:"content"`
ContentType string `json:"content_type"`
ConversationID *uint `json:"conversation_id"`
Attachments []string `json:"attachments"`
Message struct {
Content string `json:"content"`
ContentType string `json:"content_type"`
ConversationID *uint `json:"conversation_id"`
Attachments []string `json:"attachments"`
CustomAttributes map[string]any `json:"custom_attributes"`
Labels []string `json:"labels"`
Message struct {
Content string `json:"content"`
Attachments []string `json:"attachments"`
ReplyTo *uint `json:"reply_to"`
} `json:"message"`
}
if err := c.ShouldBindJSON(&body); err != nil {
@@ -1047,10 +1205,13 @@ func bindWidgetSendMessageRequest(c *gin.Context) (service.WidgetSendMessageRequ
attachments = body.Message.Attachments
}
return service.WidgetSendMessageRequest{
Content: content,
ContentType: body.ContentType,
ConversationID: body.ConversationID,
AttachmentIDs: attachments,
Content: content,
ContentType: body.ContentType,
ConversationID: body.ConversationID,
AttachmentIDs: attachments,
CustomAttributes: body.CustomAttributes,
Labels: body.Labels,
ReplyTo: body.Message.ReplyTo,
}, nil
}
@@ -1175,10 +1336,26 @@ func bindPublicContactRequest(c *gin.Context) (service.PublicContactRequest, err
}
func bindPublicMessageRequest(c *gin.Context) (service.PublicMessageRequest, error) {
if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") {
if err := c.Request.ParseMultipartForm(32 << 20); err != nil {
return service.PublicMessageRequest{}, err
}
form := c.Request.MultipartForm
attachments := form.Value["attachments[]"]
if len(attachments) == 0 {
attachments = form.Value["message[attachments][]"]
}
return service.PublicMessageRequest{
Content: firstFormValue(form.Value, "content", "message[content]"),
EchoID: firstFormValue(form.Value, "echo_id", "message[echo_id]"),
AttachmentIDs: attachments,
}, nil
}
var body struct {
Content string `json:"content"`
EchoID string `json:"echo_id"`
SubmittedValues []map[string]any `json:"submitted_values"`
Attachments []string `json:"attachments"`
}
if err := c.ShouldBindJSON(&body); err != nil {
return service.PublicMessageRequest{}, err
@@ -1187,6 +1364,7 @@ func bindPublicMessageRequest(c *gin.Context) (service.PublicMessageRequest, err
Content: body.Content,
EchoID: body.EchoID,
SubmittedValues: body.SubmittedValues,
AttachmentIDs: body.Attachments,
}, nil
}
@@ -1213,7 +1391,7 @@ func publicContactPayload(contactInbox *model.ContactInbox, contact *model.Conta
}
}
func publicConversationPayload(conversation model.Conversation, messages []model.Message) gin.H {
func (h *WidgetHandler) publicConversationPayload(ctx context.Context, conversation model.Conversation, messages []model.Message) gin.H {
payload := gin.H{
"id": publicDisplayID(conversation),
"uuid": conversation.UUID,
@@ -1225,7 +1403,11 @@ func publicConversationPayload(conversation model.Conversation, messages []model
}
messagePayloads := make([]gin.H, 0, len(messages))
for _, message := range messages {
messagePayloads = append(messagePayloads, publicMessagePayload(message, conversation))
messagePayload := publicMessagePayload(message, conversation)
if attachments, err := h.widgetService.GetMessageAttachments(ctx, message.ID); err == nil && len(attachments) > 0 {
messagePayload["attachments"] = widgetAttachmentPayloads(attachments)
}
messagePayloads = append(messagePayloads, messagePayload)
}
payload["messages"] = messagePayloads
return payload
@@ -1270,7 +1452,27 @@ func publicUnix(value *int64) int64 {
return *value
}
func isChatwootWidgetRoute(c *gin.Context) bool {
return strings.HasPrefix(c.FullPath(), "/api/v1/widget/")
}
func widgetConversationNotFoundLike(err error) bool {
if errors.Is(err, service.ErrWidgetConversationNotFound) {
return true
}
msg := err.Error()
return strings.Contains(msg, "invalid widget_token") || strings.Contains(msg, "conversation not found") || strings.Contains(msg, "record not found")
}
func widgetWebsiteTokenNotFoundLike(err error) bool {
msg := err.Error()
return strings.Contains(msg, "website_token is required") || strings.Contains(msg, "no inbox found for website_token") || strings.Contains(msg, "no web_widget inboxes found")
}
func widgetErrorStatus(err error) int {
if errors.Is(err, service.ErrWidgetMessageContentTooLong) {
return http.StatusUnprocessableEntity
}
msg := err.Error()
if strings.Contains(msg, "invalid widget_token") || strings.Contains(msg, "HMAC failed") {
return http.StatusUnauthorized