Skip to main content
Glama
message.go10.5 kB
package usecase import ( "context" "fmt" "os" "path/filepath" "time" "github.com/aldinokemal/go-whatsapp-web-multidevice/config" domainChatStorage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/chatstorage" domainMessage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" "github.com/aldinokemal/go-whatsapp-web-multidevice/infrastructure/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "github.com/sirupsen/logrus" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/proto/waCommon" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/proto/waSyncAction" "go.mau.fi/whatsmeow/types" "google.golang.org/protobuf/proto" ) type serviceMessage struct { chatStorageRepo domainChatStorage.IChatStorageRepository } func NewMessageService(chatStorageRepo domainChatStorage.IChatStorageRepository) domainMessage.IMessageUsecase { return &serviceMessage{ chatStorageRepo: chatStorageRepo, } } func (service serviceMessage) MarkAsRead(ctx context.Context, request domainMessage.MarkAsReadRequest) (response domainMessage.GenericResponse, err error) { if err = validations.ValidateMarkAsRead(ctx, request); err != nil { return response, err } dataWaRecipient, err := utils.ValidateJidWithLogin(whatsapp.GetClient(), request.Phone) if err != nil { return response, err } ids := []types.MessageID{request.MessageID} if err = whatsapp.GetClient().MarkRead(ids, time.Now(), dataWaRecipient, *whatsapp.GetClient().Store.ID); err != nil { return response, err } logrus.Info(map[string]any{ "phone": request.Phone, "message_id": request.MessageID, "chat": dataWaRecipient.String(), "sender": whatsapp.GetClient().Store.ID.String(), }) response.MessageID = request.MessageID response.Status = fmt.Sprintf("Mark as read success %s", request.MessageID) return response, nil } func (service serviceMessage) ReactMessage(ctx context.Context, request domainMessage.ReactionRequest) (response domainMessage.GenericResponse, err error) { if err = validations.ValidateReactMessage(ctx, request); err != nil { return response, err } dataWaRecipient, err := utils.ValidateJidWithLogin(whatsapp.GetClient(), request.Phone) if err != nil { return response, err } msg := &waE2E.Message{ ReactionMessage: &waE2E.ReactionMessage{ Key: &waCommon.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 := whatsapp.GetClient().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.Timestamp) return response, nil } func (service serviceMessage) RevokeMessage(ctx context.Context, request domainMessage.RevokeRequest) (response domainMessage.GenericResponse, err error) { if err = validations.ValidateRevokeMessage(ctx, request); err != nil { return response, err } dataWaRecipient, err := utils.ValidateJidWithLogin(whatsapp.GetClient(), request.Phone) if err != nil { return response, err } ts, err := whatsapp.GetClient().SendMessage(context.Background(), dataWaRecipient, whatsapp.GetClient().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.Timestamp) return response, nil } func (service serviceMessage) DeleteMessage(ctx context.Context, request domainMessage.DeleteRequest) (err error) { if err = validations.ValidateDeleteMessage(ctx, request); err != nil { return err } dataWaRecipient, err := utils.ValidateJidWithLogin(whatsapp.GetClient(), request.Phone) if err != nil { return err } isFromMe := "1" if len(request.MessageID) > 22 { isFromMe = "0" } patchInfo := appstate.PatchInfo{ Timestamp: time.Now(), Type: appstate.WAPatchRegularHigh, Mutations: []appstate.MutationInfo{{ Index: []string{appstate.IndexDeleteMessageForMe, dataWaRecipient.String(), request.MessageID, isFromMe, whatsapp.GetClient().Store.ID.String()}, Value: &waSyncAction.SyncActionValue{ DeleteMessageForMeAction: &waSyncAction.DeleteMessageForMeAction{ DeleteMedia: proto.Bool(true), MessageTimestamp: proto.Int64(time.Now().UnixMilli()), }, }, }}, } if err = whatsapp.GetClient().SendAppState(ctx, patchInfo); err != nil { return err } return nil } func (service serviceMessage) UpdateMessage(ctx context.Context, request domainMessage.UpdateMessageRequest) (response domainMessage.GenericResponse, err error) { if err = validations.ValidateUpdateMessage(ctx, request); err != nil { return response, err } dataWaRecipient, err := utils.ValidateJidWithLogin(whatsapp.GetClient(), request.Phone) if err != nil { return response, err } msg := &waE2E.Message{Conversation: proto.String(request.Message)} ts, err := whatsapp.GetClient().SendMessage(context.Background(), dataWaRecipient, whatsapp.GetClient().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.Timestamp) return response, nil } // StarMessage implements message.IMessageService. func (service serviceMessage) StarMessage(ctx context.Context, request domainMessage.StarRequest) (err error) { if err = validations.ValidateStarMessage(ctx, request); err != nil { return err } dataWaRecipient, err := utils.ValidateJidWithLogin(whatsapp.GetClient(), request.Phone) if err != nil { return err } isFromMe := true if len(request.MessageID) > 22 { isFromMe = false } patchInfo := appstate.BuildStar(dataWaRecipient.ToNonAD(), *whatsapp.GetClient().Store.ID, request.MessageID, isFromMe, request.IsStarred) if err = whatsapp.GetClient().SendAppState(ctx, patchInfo); err != nil { return err } return nil } // DownloadMedia implements message.IMessageService. func (service serviceMessage) DownloadMedia(ctx context.Context, request domainMessage.DownloadMediaRequest) (response domainMessage.DownloadMediaResponse, err error) { if err = validations.ValidateDownloadMedia(ctx, request); err != nil { return response, err } dataWaRecipient, err := utils.ValidateJidWithLogin(whatsapp.GetClient(), request.Phone) if err != nil { return response, err } // Query the message from chat storage message, err := service.chatStorageRepo.GetMessageByID(request.MessageID) if err != nil { return response, fmt.Errorf("message not found: %v", err) } if message == nil { return response, fmt.Errorf("message with ID %s not found", request.MessageID) } // Check if message has media if message.MediaType == "" || message.URL == "" { return response, fmt.Errorf("message %s does not contain downloadable media", request.MessageID) } // Verify the message is from the specified chat if message.ChatJID != dataWaRecipient.String() { return response, fmt.Errorf("message %s does not belong to chat %s", request.MessageID, dataWaRecipient.String()) } // Create directory structure for organized storage chatDir := filepath.Join(config.PathMedia, utils.ExtractPhoneNumber(message.ChatJID)) dateDir := filepath.Join(chatDir, message.Timestamp.Format("2006-01-02")) err = os.MkdirAll(dateDir, 0755) if err != nil { return response, fmt.Errorf("failed to create directory: %v", err) } // Create a downloadable message interface based on media type var downloadableMsg interface{} switch message.MediaType { case "image": downloadableMsg = &waE2E.ImageMessage{ URL: proto.String(message.URL), MediaKey: message.MediaKey, FileSHA256: message.FileSHA256, FileEncSHA256: message.FileEncSHA256, FileLength: proto.Uint64(message.FileLength), } case "video": downloadableMsg = &waE2E.VideoMessage{ URL: proto.String(message.URL), MediaKey: message.MediaKey, FileSHA256: message.FileSHA256, FileEncSHA256: message.FileEncSHA256, FileLength: proto.Uint64(message.FileLength), } case "audio": downloadableMsg = &waE2E.AudioMessage{ URL: proto.String(message.URL), MediaKey: message.MediaKey, FileSHA256: message.FileSHA256, FileEncSHA256: message.FileEncSHA256, FileLength: proto.Uint64(message.FileLength), } case "document": downloadableMsg = &waE2E.DocumentMessage{ URL: proto.String(message.URL), MediaKey: message.MediaKey, FileSHA256: message.FileSHA256, FileEncSHA256: message.FileEncSHA256, FileLength: proto.Uint64(message.FileLength), FileName: proto.String(message.Filename), } case "sticker": downloadableMsg = &waE2E.StickerMessage{ URL: proto.String(message.URL), MediaKey: message.MediaKey, FileSHA256: message.FileSHA256, FileEncSHA256: message.FileEncSHA256, FileLength: proto.Uint64(message.FileLength), } default: return response, fmt.Errorf("unsupported media type: %s", message.MediaType) } // Download the media using existing utils.ExtractMedia function extractedMedia, err := utils.ExtractMedia(ctx, whatsapp.GetClient(), dateDir, downloadableMsg.(whatsmeow.DownloadableMessage)) if err != nil { return response, fmt.Errorf("failed to download media: %v", err) } // Get file size fileInfo, err := os.Stat(extractedMedia.MediaPath) if err != nil { logrus.Warnf("Could not get file size for %s: %v", extractedMedia.MediaPath, err) } // Build response response.MessageID = request.MessageID response.Status = fmt.Sprintf("Media downloaded successfully to %s", extractedMedia.MediaPath) response.MediaType = message.MediaType response.Filename = filepath.Base(extractedMedia.MediaPath) response.FilePath = extractedMedia.MediaPath if fileInfo != nil { response.FileSize = fileInfo.Size() } logrus.Info(map[string]any{ "message_id": request.MessageID, "phone": request.Phone, "chat": dataWaRecipient.String(), "media_type": response.MediaType, "file_path": response.FilePath, "file_size": response.FileSize, }) return response, nil }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/samihalawa/whatsapp-go-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server