Browse Source

fix: make reply sending stateless

pull/239/head
almogbaku 1 year ago
parent
commit
4960103560
No known key found for this signature in database GPG Key ID: 66C92B1C5B475512
  1. 1
      src/config/settings.go
  2. 8
      src/domains/send/text.go
  3. 98
      src/pkg/utils/chat_storage.go
  4. 3
      src/pkg/whatsapp/init.go
  5. 111
      src/pkg/whatsapp/utils.go
  6. 16
      src/services/send.go
  7. 31
      src/views/components/SendMessage.js

1
src/config/settings.go

@ -16,7 +16,6 @@ var (
PathSendItems = "statics/senditems"
PathMedia = "statics/media"
PathStorages = "storages"
PathChatStorage = "storages/chat.txt"
DBURI = "file:storages/whatsapp.db?_foreign_keys=off"

8
src/domains/send/text.go

@ -3,5 +3,11 @@ package send
type MessageRequest struct {
Phone string `json:"phone" form:"phone"`
Message string `json:"message" form:"message"`
ReplyMessageID *string `json:"reply_message_id" form:"reply_message_id"`
Reply *ReplyMessageRequest `json:"reply" form:"reply"`
}
type ReplyMessageRequest struct {
ReplyMessageID string `json:"reply_message_id" form:"reply_message_id"`
ParticipantJID string `json:"participant_jid" form:"participant_jid"`
Quote string `json:"quote" form:"quote"`
}

98
src/pkg/utils/chat_storage.go

@ -1,98 +0,0 @@
package utils
import (
"fmt"
"os"
"strings"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
"github.com/gofiber/fiber/v2/log"
)
type RecordedMessage struct {
MessageID string `json:"message_id,omitempty"`
JID string `json:"jid,omitempty"`
MessageContent string `json:"message_content,omitempty"`
}
func FindRecordFromStorage(messageID string) (RecordedMessage, error) {
data, err := os.ReadFile(config.PathChatStorage)
if err != nil {
return RecordedMessage{}, err
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if line == "" {
continue
}
parts := strings.Split(line, ",")
if len(parts) == 3 && parts[0] == messageID {
return RecordedMessage{
MessageID: parts[0],
JID: parts[1],
MessageContent: parts[2],
}, nil
}
}
return RecordedMessage{}, fmt.Errorf("message ID %s not found in storage", messageID)
}
func RecordMessage(messageID string, senderJID string, messageContent string) {
message := RecordedMessage{
MessageID: messageID,
JID: senderJID,
MessageContent: messageContent,
}
// Read existing messages
var messages []RecordedMessage
if data, err := os.ReadFile(config.PathChatStorage); err == nil {
// Split file by newlines and parse each line
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if line == "" {
continue
}
parts := strings.Split(line, ",")
msg := RecordedMessage{
MessageID: parts[0],
JID: parts[1],
MessageContent: parts[2],
}
messages = append(messages, msg)
}
}
// Check for duplicates
for _, msg := range messages {
if msg.MessageID == message.MessageID {
return // Skip if duplicate found
}
}
// Write new message at the top
f, err := os.OpenFile(config.PathChatStorage, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
log.Errorf("Failed to open received-chat.txt: %v", err)
return
}
defer f.Close()
// Write new message first
csvLine := fmt.Sprintf("%s,%s,%s\n", message.MessageID, message.JID, message.MessageContent)
if _, err := f.WriteString(csvLine); err != nil {
log.Errorf("Failed to write to received-chat.txt: %v", err)
return
}
// Write existing messages after
for _, msg := range messages {
csvLine := fmt.Sprintf("%s,%s,%s\n", msg.MessageID, msg.JID, msg.MessageContent)
if _, err := f.WriteString(csvLine); err != nil {
log.Errorf("Failed to write to received-chat.txt: %v", err)
return
}
}
}

3
src/pkg/whatsapp/init.go

@ -12,7 +12,6 @@ import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
"github.com/aldinokemal/go-whatsapp-web-multidevice/internal/websocket"
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils"
"github.com/sirupsen/logrus"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/appstate"
@ -150,8 +149,6 @@ func handler(rawEvt interface{}) {
}
log.Infof("Received message %s from %s (%s): %+v", evt.Info.ID, evt.Info.SourceString(), strings.Join(metaParts, ", "), evt.Message)
message := ExtractMessageText(evt)
utils.RecordMessage(evt.Info.ID, evt.Info.Sender.String(), message)
if img := evt.Message.GetImageMessage(); img != nil {
if path, err := ExtractMedia(config.PathStorages, img); err != nil {

111
src/pkg/whatsapp/utils.go

@ -250,114 +250,3 @@ func buildForwarded(evt *events.Message) bool {
}
return false
}
func ExtractMessageText(evt *events.Message) string {
messageText := evt.Message.GetConversation()
if extendedText := evt.Message.GetExtendedTextMessage(); extendedText != nil {
messageText = extendedText.GetText()
} else if protocolMessage := evt.Message.GetProtocolMessage(); protocolMessage != nil {
if editedMessage := protocolMessage.GetEditedMessage(); editedMessage != nil {
if extendedText := editedMessage.GetExtendedTextMessage(); extendedText != nil {
messageText = extendedText.GetText()
}
}
} else if imageMessage := evt.Message.GetImageMessage(); imageMessage != nil {
messageText = imageMessage.GetCaption()
if messageText == "" {
messageText = "🖼️ Image"
} else {
messageText = "🖼️ " + messageText
}
} else if documentMessage := evt.Message.GetDocumentMessage(); documentMessage != nil {
messageText = documentMessage.GetCaption()
if messageText == "" {
messageText = "📄 Document"
} else {
messageText = "📄 " + messageText
}
} else if videoMessage := evt.Message.GetVideoMessage(); videoMessage != nil {
messageText = videoMessage.GetCaption()
if messageText == "" {
messageText = "🎥 Video"
} else {
messageText = "🎥 " + messageText
}
} else if liveLocationMessage := evt.Message.GetLiveLocationMessage(); liveLocationMessage != nil {
messageText = liveLocationMessage.GetCaption()
if messageText == "" {
messageText = "📍 Live Location"
} else {
messageText = "📍 " + messageText
}
} else if locationMessage := evt.Message.GetLocationMessage(); locationMessage != nil {
messageText = locationMessage.GetName()
if messageText == "" {
messageText = "📍 Location"
} else {
messageText = "📍 " + messageText
}
} else if stickerMessage := evt.Message.GetStickerMessage(); stickerMessage != nil {
messageText = "🎨 Sticker"
if stickerMessage.GetIsAnimated() {
messageText = "✨ Animated Sticker"
}
if stickerMessage.GetAccessibilityLabel() != "" {
messageText += " - " + stickerMessage.GetAccessibilityLabel()
}
} else if contactMessage := evt.Message.GetContactMessage(); contactMessage != nil {
messageText = contactMessage.GetDisplayName()
if messageText == "" {
messageText = "👤 Contact"
} else {
messageText = "👤 " + messageText
}
} else if listMessage := evt.Message.GetListMessage(); listMessage != nil {
messageText = listMessage.GetTitle()
if messageText == "" {
messageText = "📝 List"
} else {
messageText = "📝 " + messageText
}
} else if orderMessage := evt.Message.GetOrderMessage(); orderMessage != nil {
messageText = orderMessage.GetOrderTitle()
if messageText == "" {
messageText = "🛍️ Order"
} else {
messageText = "🛍️ " + messageText
}
} else if paymentMessage := evt.Message.GetPaymentInviteMessage(); paymentMessage != nil {
messageText = paymentMessage.GetServiceType().String()
if messageText == "" {
messageText = "💳 Payment"
} else {
messageText = "💳 " + messageText
}
} else if audioMessage := evt.Message.GetAudioMessage(); audioMessage != nil {
messageText = "🎧 Audio"
if audioMessage.GetPTT() {
messageText = "🎤 Voice Message"
}
} else if pollMessageV3 := evt.Message.GetPollCreationMessageV3(); pollMessageV3 != nil {
messageText = pollMessageV3.GetName()
if messageText == "" {
messageText = "📊 Poll"
} else {
messageText = "📊 " + messageText
}
} else if pollMessageV4 := evt.Message.GetPollCreationMessageV4(); pollMessageV4 != nil {
messageText = pollMessageV4.GetMessage().GetConversation()
if messageText == "" {
messageText = "📊 Poll"
} else {
messageText = "📊 " + messageText
}
} else if pollMessageV5 := evt.Message.GetPollCreationMessageV5(); pollMessageV5 != nil {
messageText = pollMessageV5.GetMessage().GetConversation()
if messageText == "" {
messageText = "📊 Poll"
} else {
messageText = "📊 " + messageText
}
}
return messageText
}

16
src/services/send.go

@ -44,8 +44,6 @@ func (service serviceSend) wrapSendMessage(ctx context.Context, recipient types.
return whatsmeow.SendResponse{}, err
}
utils.RecordMessage(ts.ID, service.WaCli.Store.ID.String(), content)
return ts, nil
}
@ -74,16 +72,15 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess
}
// Reply message
if request.ReplyMessageID != nil && *request.ReplyMessageID != "" {
record, err := utils.FindRecordFromStorage(*request.ReplyMessageID)
if err == nil { // Only set reply context if we found the message ID
if request.Reply != nil {
rply := *request.Reply
msg.ExtendedTextMessage = &waE2E.ExtendedTextMessage{
Text: proto.String(request.Message),
ContextInfo: &waE2E.ContextInfo{
StanzaID: request.ReplyMessageID,
Participant: proto.String(record.JID),
StanzaID: &rply.ReplyMessageID,
Participant: proto.String(rply.ParticipantJID),
QuotedMessage: &waE2E.Message{
Conversation: proto.String(record.MessageContent),
Conversation: proto.String(rply.Quote),
},
},
}
@ -91,9 +88,6 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess
if len(parsedMentions) > 0 {
msg.ExtendedTextMessage.ContextInfo.MentionedJID = parsedMentions
}
} else {
logrus.Warnf("Reply message ID %s not found in storage, continuing without reply context", *request.ReplyMessageID)
}
}
ts, err := service.wrapSendMessage(ctx, dataWaRecipient, msg, request.Message)

31
src/views/components/SendMessage.js

@ -11,6 +11,8 @@ export default {
phone: '',
text: '',
reply_message_id: '',
participant_jid: '',
quote: '',
loading: false,
}
},
@ -38,10 +40,11 @@ export default {
const isMessageValid = this.text.trim().length > 0 && this.text.length <= 4096;
// Validate reply_message_id format if provided
const isReplyIdValid = this.reply_message_id === '' ||
/^[A-F0-9]{32}\/[A-F0-9]{20}$/.test(this.reply_message_id);
const isReplyValid = this.reply_message_id === '' ||
/^[A-F0-9]{32}\/[A-F0-9]{20}$/.test(this.reply_message_id) &&
this.participant_jid.trim().length > 0 && this.quote.trim().length > 0;
return isPhoneValid && isMessageValid && isReplyIdValid;
return isPhoneValid && isMessageValid && isReplyValid;
},
async handleSubmit() {
// Add validation check here to prevent submission when form is invalid
@ -62,10 +65,12 @@ export default {
const payload = {
phone: this.phone_id,
message: this.text.trim(),
};
if (this.reply_message_id !== '') {
payload.reply_message_id = this.reply_message_id;
reply: {
reply_message_id: this.reply_message_id,
participant_jid: this.participant_jid,
quote: this.quote
}
};
const response = await window.http.post('/send/message', payload);
this.handleReset();
@ -83,6 +88,8 @@ export default {
this.phone = '';
this.text = '';
this.reply_message_id = '';
this.participant_jid = '';
this.quote = '';
},
},
template: `
@ -111,6 +118,18 @@ export default {
placeholder="Optional: 57D29F74B7FC62F57D8AC2C840279B5B/3EB0288F008D32FCD0A424"
aria-label="reply_message_id">
</div>
<div class="field" v-if="isShowReplyId() && reply_message_id !== ''">
<label>Participant JID</label>
<input v-model="participant_jid" type="text"
placeholder="Quoted Participant JID"
aria-label="participant_jid" required>
</div>
<div class="field" v-if="isShowReplyId() && reply_message_id !== ''">
<label>Quote</label>
<input v-model="quote" type="text"
placeholder="Quote"
aria-label="quote" required>
</div>
<div class="field">
<label>Message</label>
<textarea v-model="text" placeholder="Hello this is message text"

Loading…
Cancel
Save