From 36496b3246f469d365a77a9a2bde45db737bf761 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sat, 24 May 2025 10:22:28 +0700 Subject: [PATCH 1/7] feat: update go lib --- src/cmd/root.go | 6 ++- src/config/settings.go | 2 +- src/go.mod | 8 ++-- src/go.sum | 8 ++++ src/pkg/whatsapp/init.go | 76 +++++++++++++++++++------------------ src/pkg/whatsapp/utils.go | 5 ++- src/pkg/whatsapp/webhook.go | 17 +++++---- src/services/app.go | 12 +++--- src/services/message.go | 4 +- src/services/user.go | 8 ++-- 10 files changed, 80 insertions(+), 66 deletions(-) diff --git a/src/cmd/root.go b/src/cmd/root.go index 42d42a2..750ac5f 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "embed" "fmt" "log" @@ -230,8 +231,9 @@ func runRest(_ *cobra.Command, _ []string) { })) } - db := whatsapp.InitWaDB() - cli := whatsapp.InitWaCLI(db) + ctx := context.Background() + db := whatsapp.InitWaDB(ctx) + cli := whatsapp.InitWaCLI(ctx, db) // Service appService := services.NewAppService(cli, db) diff --git a/src/config/settings.go b/src/config/settings.go index e5aaa6f..7c967d9 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -5,7 +5,7 @@ import ( ) var ( - AppVersion = "v5.6.1" + AppVersion = "v5.6.2" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" diff --git a/src/go.mod b/src/go.mod index 9cd323f..c9ce1a2 100644 --- a/src/go.mod +++ b/src/go.mod @@ -7,7 +7,7 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/dustin/go-humanize v1.0.1 github.com/go-ozzo/ozzo-validation/v4 v4.3.0 - github.com/gofiber/fiber/v2 v2.52.6 + github.com/gofiber/fiber/v2 v2.52.8 github.com/gofiber/template/html/v2 v2.1.3 github.com/gofiber/websocket/v2 v2.2.1 github.com/google/uuid v1.6.0 @@ -19,8 +19,8 @@ require ( github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 github.com/valyala/fasthttp v1.62.0 - go.mau.fi/libsignal v0.1.2 - go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa + go.mau.fi/libsignal v0.2.0 + go.mau.fi/whatsmeow v0.0.0-20250521125706-91ac75c2f61a golang.org/x/image v0.27.0 google.golang.org/protobuf v1.36.6 ) @@ -55,7 +55,7 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - go.mau.fi/util v0.8.6 // indirect + go.mau.fi/util v0.8.7 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.38.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect diff --git a/src/go.sum b/src/go.sum index 7693be3..7f9a58b 100644 --- a/src/go.sum +++ b/src/go.sum @@ -36,6 +36,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4= +github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6dFLp8ea+o= @@ -144,8 +146,12 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= +go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= +go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= +go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= +go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= go.mau.fi/whatsmeow v0.0.0-20250318233852-06705625cf82 h1:AZlDkXHgoQNW4gd2hnTCvPH7hYznmwc3gPaYqGZ5w8A= go.mau.fi/whatsmeow v0.0.0-20250318233852-06705625cf82/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs= go.mau.fi/whatsmeow v0.0.0-20250402091807-b0caa1b76088 h1:ns6nk2NjqdaQnCKrp+Qqwpf+3OI7+nnH56D71+7XzOM= @@ -154,6 +160,8 @@ go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526 h1:i9w16FdM3zmOWdF5nh1l2M go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa h1:+bQKfMtnhX2jVoCSaneH4Ctk51IVT1K2gvjyqfFjVW0= go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= +go.mau.fi/whatsmeow v0.0.0-20250521125706-91ac75c2f61a h1:PYtzz5wdma64I47CiquGicyubzg3HIPkH/jMzpmHu8g= +go.mau.fi/whatsmeow v0.0.0-20250521125706-91ac75c2f61a/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/src/pkg/whatsapp/init.go b/src/pkg/whatsapp/init.go index f5ecc4f..3f28c1a 100644 --- a/src/pkg/whatsapp/init.go +++ b/src/pkg/whatsapp/init.go @@ -53,11 +53,11 @@ var ( ) // InitWaDB initializes the WhatsApp database connection -func InitWaDB() *sqlstore.Container { +func InitWaDB(ctx context.Context) *sqlstore.Container { log = waLog.Stdout("Main", config.WhatsappLogLevel, true) dbLog := waLog.Stdout("Database", config.WhatsappLogLevel, true) - storeContainer, err := initDatabase(dbLog) + storeContainer, err := initDatabase(ctx, dbLog) if err != nil { log.Errorf("Database initialization error: %v", err) panic(pkgError.InternalServerError(fmt.Sprintf("Database initialization error: %v", err))) @@ -67,19 +67,19 @@ func InitWaDB() *sqlstore.Container { } // initDatabase creates and returns a database store container based on the configured URI -func initDatabase(dbLog waLog.Logger) (*sqlstore.Container, error) { +func initDatabase(ctx context.Context, dbLog waLog.Logger) (*sqlstore.Container, error) { if strings.HasPrefix(config.DBURI, "file:") { - return sqlstore.New("sqlite3", config.DBURI, dbLog) + return sqlstore.New(ctx, "sqlite3", config.DBURI, dbLog) } else if strings.HasPrefix(config.DBURI, "postgres:") { - return sqlstore.New("postgres", config.DBURI, dbLog) + return sqlstore.New(ctx, "postgres", config.DBURI, dbLog) } return nil, fmt.Errorf("unknown database type: %s. Currently only sqlite3(file:) and postgres are supported", config.DBURI) } // InitWaCLI initializes the WhatsApp client -func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client { - device, err := storeContainer.GetFirstDevice() +func InitWaCLI(ctx context.Context, storeContainer *sqlstore.Container) *whatsmeow.Client { + device, err := storeContainer.GetFirstDevice(ctx) if err != nil { log.Errorf("Failed to get device: %v", err) panic(err) @@ -99,46 +99,48 @@ func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client { cli = whatsmeow.NewClient(device, waLog.Stdout("Client", config.WhatsappLogLevel, true)) cli.EnableAutoReconnect = true cli.AutoTrustIdentity = true - cli.AddEventHandler(handler) + cli.AddEventHandler(func(rawEvt interface{}) { + handler(ctx, rawEvt) + }) return cli } // handler is the main event handler for WhatsApp events -func handler(rawEvt interface{}) { +func handler(ctx context.Context, rawEvt interface{}) { switch evt := rawEvt.(type) { case *events.DeleteForMe: - handleDeleteForMe(evt) + handleDeleteForMe(ctx, evt) case *events.AppStateSyncComplete: - handleAppStateSyncComplete(evt) + handleAppStateSyncComplete(ctx, evt) case *events.PairSuccess: - handlePairSuccess(evt) + handlePairSuccess(ctx, evt) case *events.LoggedOut: - handleLoggedOut() + handleLoggedOut(ctx) case *events.Connected, *events.PushNameSetting: - handleConnectionEvents() + handleConnectionEvents(ctx) case *events.StreamReplaced: - handleStreamReplaced() + handleStreamReplaced(ctx) case *events.Message: - handleMessage(evt) + handleMessage(ctx, evt) case *events.Receipt: - handleReceipt(evt) + handleReceipt(ctx, evt) case *events.Presence: - handlePresence(evt) + handlePresence(ctx, evt) case *events.HistorySync: - handleHistorySync(evt) + handleHistorySync(ctx, evt) case *events.AppState: - handleAppState(evt) + handleAppState(ctx, evt) } } // Event handler functions -func handleDeleteForMe(evt *events.DeleteForMe) { +func handleDeleteForMe(_ context.Context, evt *events.DeleteForMe) { log.Infof("Deleted message %s for %s", evt.MessageID, evt.SenderJID.String()) } -func handleAppStateSyncComplete(evt *events.AppStateSyncComplete) { +func handleAppStateSyncComplete(_ context.Context, evt *events.AppStateSyncComplete) { if len(cli.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { if err := cli.SendPresence(types.PresenceAvailable); err != nil { log.Warnf("Failed to send available presence: %v", err) @@ -148,21 +150,21 @@ func handleAppStateSyncComplete(evt *events.AppStateSyncComplete) { } } -func handlePairSuccess(evt *events.PairSuccess) { +func handlePairSuccess(_ context.Context, evt *events.PairSuccess) { websocket.Broadcast <- websocket.BroadcastMessage{ Code: "LOGIN_SUCCESS", Message: fmt.Sprintf("Successfully pair with %s", evt.ID.String()), } } -func handleLoggedOut() { +func handleLoggedOut(_ context.Context) { websocket.Broadcast <- websocket.BroadcastMessage{ Code: "LIST_DEVICES", Result: nil, } } -func handleConnectionEvents() { +func handleConnectionEvents(_ context.Context) { if len(cli.Store.PushName) == 0 { return } @@ -176,11 +178,11 @@ func handleConnectionEvents() { } } -func handleStreamReplaced() { +func handleStreamReplaced(_ context.Context) { os.Exit(0) } -func handleMessage(evt *events.Message) { +func handleMessage(ctx context.Context, evt *events.Message) { // Log message metadata metaParts := buildMessageMetaParts(evt) log.Infof("Received message %s from %s (%s): %+v", @@ -195,13 +197,13 @@ func handleMessage(evt *events.Message) { utils.RecordMessage(evt.Info.ID, evt.Info.Sender.String(), message) // Handle image message if present - handleImageMessage(evt) + handleImageMessage(ctx, evt) // Handle auto-reply if configured handleAutoReply(evt) // Forward to webhook if configured - handleWebhookForward(evt) + handleWebhookForward(ctx, evt) } func buildMessageMetaParts(evt *events.Message) []string { @@ -221,9 +223,9 @@ func buildMessageMetaParts(evt *events.Message) []string { return metaParts } -func handleImageMessage(evt *events.Message) { +func handleImageMessage(ctx context.Context, evt *events.Message) { if img := evt.Message.GetImageMessage(); img != nil { - if path, err := ExtractMedia(config.PathStorages, img); err != nil { + if path, err := ExtractMedia(ctx, config.PathStorages, img); err != nil { log.Errorf("Failed to download image: %v", err) } else { log.Infof("Image downloaded to %s", path) @@ -244,19 +246,19 @@ func handleAutoReply(evt *events.Message) { } } -func handleWebhookForward(evt *events.Message) { +func handleWebhookForward(ctx context.Context, evt *events.Message) { if len(config.WhatsappWebhook) > 0 && !strings.Contains(evt.Info.SourceString(), "broadcast") && !isFromMySelf(evt.Info.SourceString()) { go func(evt *events.Message) { - if err := forwardToWebhook(evt); err != nil { + if err := forwardToWebhook(ctx, evt); err != nil { logrus.Error("Failed forward to webhook: ", err) } }(evt) } } -func handleReceipt(evt *events.Receipt) { +func handleReceipt(_ context.Context, evt *events.Receipt) { if evt.Type == types.ReceiptTypeRead || evt.Type == types.ReceiptTypeReadSelf { log.Infof("%v was read by %s at %s", evt.MessageIDs, evt.SourceString(), evt.Timestamp) } else if evt.Type == types.ReceiptTypeDelivered { @@ -264,7 +266,7 @@ func handleReceipt(evt *events.Receipt) { } } -func handlePresence(evt *events.Presence) { +func handlePresence(_ context.Context, evt *events.Presence) { if evt.Unavailable { if evt.LastSeen.IsZero() { log.Infof("%s is now offline", evt.From) @@ -276,7 +278,7 @@ func handlePresence(evt *events.Presence) { } } -func handleHistorySync(evt *events.HistorySync) { +func handleHistorySync(_ context.Context, evt *events.HistorySync) { id := atomic.AddInt32(&historySyncID, 1) fileName := fmt.Sprintf("%s/history-%d-%s-%d-%s.json", config.PathStorages, @@ -303,6 +305,6 @@ func handleHistorySync(evt *events.HistorySync) { log.Infof("Wrote history sync to %s", fileName) } -func handleAppState(evt *events.AppState) { +func handleAppState(ctx context.Context, evt *events.AppState) { log.Debugf("App state event: %+v / %+v", evt.Index, evt.SyncActionValue) } diff --git a/src/pkg/whatsapp/utils.go b/src/pkg/whatsapp/utils.go index a4e84ca..2205126 100644 --- a/src/pkg/whatsapp/utils.go +++ b/src/pkg/whatsapp/utils.go @@ -1,6 +1,7 @@ package whatsapp import ( + "context" "crypto/hmac" "crypto/sha256" "encoding/hex" @@ -23,13 +24,13 @@ import ( ) // ExtractMedia is a helper function to extract media from whatsapp -func ExtractMedia(storageLocation string, mediaFile whatsmeow.DownloadableMessage) (extractedMedia ExtractedMedia, err error) { +func ExtractMedia(ctx context.Context, storageLocation string, mediaFile whatsmeow.DownloadableMessage) (extractedMedia ExtractedMedia, err error) { if mediaFile == nil { logrus.Info("Skip download because data is nil") return extractedMedia, nil } - data, err := cli.Download(mediaFile) + data, err := cli.Download(ctx, mediaFile) if err != nil { return extractedMedia, err } diff --git a/src/pkg/whatsapp/webhook.go b/src/pkg/whatsapp/webhook.go index 5a1dc24..d70a16d 100644 --- a/src/pkg/whatsapp/webhook.go +++ b/src/pkg/whatsapp/webhook.go @@ -2,6 +2,7 @@ package whatsapp import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -14,9 +15,9 @@ import ( ) // forwardToWebhook is a helper function to forward event to webhook url -func forwardToWebhook(evt *events.Message) error { +func forwardToWebhook(ctx context.Context, evt *events.Message) error { logrus.Info("Forwarding event to webhook:", config.WhatsappWebhook) - payload, err := createPayload(evt) + payload, err := createPayload(ctx, evt) if err != nil { return err } @@ -31,7 +32,7 @@ func forwardToWebhook(evt *events.Message) error { return nil } -func createPayload(evt *events.Message) (map[string]interface{}, error) { +func createPayload(ctx context.Context, evt *events.Message) (map[string]interface{}, error) { message := buildEventMessage(evt) waReaction := buildEventReaction(evt) forwarded := buildForwarded(evt) @@ -61,7 +62,7 @@ func createPayload(evt *events.Message) (map[string]interface{}, error) { } if audioMedia := evt.Message.GetAudioMessage(); audioMedia != nil { - path, err := ExtractMedia(config.PathMedia, audioMedia) + path, err := ExtractMedia(ctx, config.PathMedia, audioMedia) if err != nil { logrus.Errorf("Failed to download audio from %s: %v", evt.Info.SourceString(), err) return nil, pkgError.WebhookError(fmt.Sprintf("Failed to download audio: %v", err)) @@ -74,7 +75,7 @@ func createPayload(evt *events.Message) (map[string]interface{}, error) { } if documentMedia := evt.Message.GetDocumentMessage(); documentMedia != nil { - path, err := ExtractMedia(config.PathMedia, documentMedia) + path, err := ExtractMedia(ctx, config.PathMedia, documentMedia) if err != nil { logrus.Errorf("Failed to download document from %s: %v", evt.Info.SourceString(), err) return nil, pkgError.WebhookError(fmt.Sprintf("Failed to download document: %v", err)) @@ -83,7 +84,7 @@ func createPayload(evt *events.Message) (map[string]interface{}, error) { } if imageMedia := evt.Message.GetImageMessage(); imageMedia != nil { - path, err := ExtractMedia(config.PathMedia, imageMedia) + path, err := ExtractMedia(ctx, config.PathMedia, imageMedia) if err != nil { logrus.Errorf("Failed to download image from %s: %v", evt.Info.SourceString(), err) return nil, pkgError.WebhookError(fmt.Sprintf("Failed to download image: %v", err)) @@ -108,7 +109,7 @@ func createPayload(evt *events.Message) (map[string]interface{}, error) { } if stickerMedia := evt.Message.GetStickerMessage(); stickerMedia != nil { - path, err := ExtractMedia(config.PathMedia, stickerMedia) + path, err := ExtractMedia(ctx, config.PathMedia, stickerMedia) if err != nil { logrus.Errorf("Failed to download sticker from %s: %v", evt.Info.SourceString(), err) return nil, pkgError.WebhookError(fmt.Sprintf("Failed to download sticker: %v", err)) @@ -117,7 +118,7 @@ func createPayload(evt *events.Message) (map[string]interface{}, error) { } if videoMedia := evt.Message.GetVideoMessage(); videoMedia != nil { - path, err := ExtractMedia(config.PathMedia, videoMedia) + path, err := ExtractMedia(ctx, config.PathMedia, videoMedia) if err != nil { logrus.Errorf("Failed to download video from %s: %v", evt.Info.SourceString(), err) return nil, pkgError.WebhookError(fmt.Sprintf("Failed to download video: %v", err)) diff --git a/src/services/app.go b/src/services/app.go index a52b66a..51466dc 100644 --- a/src/services/app.go +++ b/src/services/app.go @@ -107,7 +107,7 @@ func (service serviceApp) LoginWithCode(ctx context.Context, phoneNumber string) // reconnect first _ = service.Reconnect(ctx) - loginCode, err = service.WaCli.PairPhone(phoneNumber, true, whatsmeow.PairClientChrome, "Chrome (Linux)") + loginCode, err = service.WaCli.PairPhone(ctx, phoneNumber, true, whatsmeow.PairClientChrome, "Chrome (Linux)") if err != nil { logrus.Errorf("Error when pairing phone: %s", err.Error()) return loginCode, err @@ -117,7 +117,7 @@ func (service serviceApp) LoginWithCode(ctx context.Context, phoneNumber string) return loginCode, nil } -func (service serviceApp) Logout(_ context.Context) (err error) { +func (service serviceApp) Logout(ctx context.Context) (err error) { // delete history files, err := filepath.Glob(fmt.Sprintf("./%s/history-*", config.PathStorages)) if err != nil { @@ -158,7 +158,7 @@ func (service serviceApp) Logout(_ context.Context) (err error) { } } - err = service.WaCli.Logout() + err = service.WaCli.Logout(ctx) return } @@ -172,7 +172,7 @@ func (service serviceApp) FirstDevice(ctx context.Context) (response domainApp.D return response, pkgError.ErrWaCLI } - devices, err := service.db.GetFirstDevice() + devices, err := service.db.GetFirstDevice(ctx) if err != nil { return response, err } @@ -187,12 +187,12 @@ func (service serviceApp) FirstDevice(ctx context.Context) (response domainApp.D return response, nil } -func (service serviceApp) FetchDevices(_ context.Context) (response []domainApp.DevicesResponse, err error) { +func (service serviceApp) FetchDevices(ctx context.Context) (response []domainApp.DevicesResponse, err error) { if service.WaCli == nil { return response, pkgError.ErrWaCLI } - devices, err := service.db.GetAllDevices() + devices, err := service.db.GetAllDevices(ctx) if err != nil { return nil, err } diff --git a/src/services/message.go b/src/services/message.go index 9d16da1..9433fac 100644 --- a/src/services/message.go +++ b/src/services/message.go @@ -131,7 +131,7 @@ func (service serviceMessage) DeleteMessage(ctx context.Context, request domainM }}, } - if err = service.WaCli.SendAppState(patchInfo); err != nil { + if err = service.WaCli.SendAppState(ctx, patchInfo); err != nil { return err } return nil @@ -176,7 +176,7 @@ func (service serviceMessage) StarMessage(ctx context.Context, request domainMes patchInfo := appstate.BuildStar(dataWaRecipient.ToNonAD(), *service.WaCli.Store.ID, request.MessageID, isFromMe, request.IsStarred) - if err = service.WaCli.SendAppState(patchInfo); err != nil { + if err = service.WaCli.SendAppState(ctx, patchInfo); err != nil { return err } return nil diff --git a/src/services/user.go b/src/services/user.go index 12e7f1f..91a6bad 100644 --- a/src/services/user.go +++ b/src/services/user.go @@ -146,10 +146,10 @@ func (service userService) MyListNewsletter(_ context.Context) (response domainU return response, nil } -func (service userService) MyPrivacySetting(_ context.Context) (response domainUser.MyPrivacySettingResponse, err error) { +func (service userService) MyPrivacySetting(ctx context.Context) (response domainUser.MyPrivacySettingResponse, err error) { whatsapp.MustLogin(service.WaCli) - resp, err := service.WaCli.TryFetchPrivacySettings(true) + resp, err := service.WaCli.TryFetchPrivacySettings(ctx, true) if err != nil { return } @@ -164,7 +164,7 @@ func (service userService) MyPrivacySetting(_ context.Context) (response domainU func (service userService) MyListContacts(ctx context.Context) (response domainUser.MyListContactsResponse, err error) { whatsapp.MustLogin(service.WaCli) - contacts, err := service.WaCli.Store.Contacts.GetAllContacts() + contacts, err := service.WaCli.Store.Contacts.GetAllContacts(ctx) if err != nil { return } @@ -236,7 +236,7 @@ func (service userService) ChangeAvatar(ctx context.Context, request domainUser. func (service userService) ChangePushName(ctx context.Context, request domainUser.ChangePushNameRequest) (err error) { whatsapp.MustLogin(service.WaCli) - err = service.WaCli.SendAppState(appstate.BuildSettingPushName(request.PushName)) + err = service.WaCli.SendAppState(ctx, appstate.BuildSettingPushName(request.PushName)) if err != nil { return err } From 84805f0dad34fbc5490b63c29011d756e8eaf00b Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sun, 25 May 2025 11:17:32 +0700 Subject: [PATCH 2/7] feat: adding MCP (SSE) to communicate with AI Agent commit 9a21f32d7375f62850c7f32f81924fca96490d2a Author: Aldino Kemal Date: Sun May 25 10:32:50 2025 +0700 feat: add image sending functionality to WhatsApp MCP server - Introduced a new tool for sending images to WhatsApp contacts or groups. - Implemented handler functions to process image sending requests, including parameters for phone number, image URL, caption, and additional options. - Enhanced the MCP server capabilities with image handling features. commit 936069e068c8e8fe1a89a6c07d68455136419e68 Author: Aldino Kemal Date: Sat May 24 18:47:36 2025 +0700 fix: update Send Poll image URL in README to include versioning for cache busting commit 409742dfec25d29ca9089cce9b23f4e4125839ee Author: Aldino Kemal Date: Sat May 24 18:45:54 2025 +0700 refactor: reorganize HTTP REST API section in README for clarity commit df1889ad5b1918c46667e7f17395dd713192c9a8 Author: Aldino Kemal Date: Sat May 24 18:42:09 2025 +0700 docs: enhance README with additional MCP UI details and formatting improvements commit 74b97f2dd674eb5cb3284f7c455a2935bb35c530 Author: Aldino Kemal Date: Sat May 24 18:31:44 2025 +0700 feat: update README to clarify download instructions and add command for service commit c24991ddf04a02db2d8085ec3db7f80cd01afb99 Author: Aldino Kemal Date: Sat May 24 18:29:01 2025 +0700 feat: enhance README with MCP server details and configuration commit 8ef03eb1e4a4a0379bd8168aeeb145901cbc85a2 Author: Aldino Kemal Date: Sat May 24 18:03:41 2025 +0700 feat: enhance MCP server with new WhatsApp tools - Added new tools for sending messages, contacts, links, and locations via WhatsApp. - Updated the project structure documentation to include the new MCP server directory. - Implemented corresponding handler functions for each new tool to process requests and return results. commit dad99583e9e1561bdec33b73309dd5a15ee1ba0a Author: Aldino Kemal Date: Sat May 24 16:01:50 2025 +0700 feat: implement WhatsApp MCP server with SSE support - Added a new command to start the WhatsApp MCP server using Server-Sent Events (SSE). - Introduced configuration options for MCP server host and port. - Implemented message sending functionality through the MCP server. - Updated application version to v6.0.0. - Added new dependencies for MCP server functionality in go.mod and go.sum. commit efcdcec0874aa25b9122684f2190dadb661dc56c Author: Aldino Kemal Date: Sat May 24 11:05:58 2025 +0700 feat: restructure folder --- .cursor/rules/project-structure.mdc | 122 +++++++ readme.md | 79 +++- src/cmd/mcp.go | 69 ++++ src/cmd/rest.go | 122 +++++++ src/cmd/root.go | 168 +++------ src/config/settings.go | 5 +- src/domains/app/app.go | 2 +- src/domains/group/group.go | 2 +- src/domains/message/message.go | 2 +- src/domains/newsletter/newsletter.go | 2 +- src/domains/send/send.go | 2 +- src/domains/user/user.go | 2 +- src/go.mod | 13 + src/go.sum | 94 ++--- src/{pkg => infrastructure}/whatsapp/init.go | 2 +- src/{pkg => infrastructure}/whatsapp/utils.go | 0 .../whatsapp/webhook.go | 0 src/ui/mcp/send.go | 338 ++++++++++++++++++ src/{internal => ui}/rest/app.go | 5 +- src/{internal => ui}/rest/group.go | 6 +- src/{internal => ui}/rest/helpers/common.go | 7 +- .../rest/helpers/flushChatCsv.go | 0 src/{internal => ui}/rest/message.go | 6 +- .../rest/middleware/basicauth.go | 0 .../rest/middleware/recovery.go | 0 src/{internal => ui}/rest/newsletter.go | 4 +- src/{internal => ui}/rest/send.go | 6 +- src/{internal => ui}/rest/user.go | 6 +- src/{internal => ui}/websocket/websocket.go | 2 +- src/{services => usecase}/app.go | 4 +- src/{services => usecase}/group.go | 24 +- src/{services => usecase}/message.go | 6 +- src/{services => usecase}/newsletter.go | 13 +- src/{services => usecase}/send.go | 10 +- src/{services => usecase}/user.go | 26 +- 35 files changed, 887 insertions(+), 262 deletions(-) create mode 100644 .cursor/rules/project-structure.mdc create mode 100644 src/cmd/mcp.go create mode 100644 src/cmd/rest.go rename src/{pkg => infrastructure}/whatsapp/init.go (99%) rename src/{pkg => infrastructure}/whatsapp/utils.go (100%) rename src/{pkg => infrastructure}/whatsapp/webhook.go (100%) create mode 100644 src/ui/mcp/send.go rename src/{internal => ui}/rest/app.go (95%) rename src/{internal => ui}/rest/group.go (96%) rename src/{internal => ui}/rest/helpers/common.go (91%) rename src/{internal => ui}/rest/helpers/flushChatCsv.go (100%) rename src/{internal => ui}/rest/message.go (96%) rename src/{internal => ui}/rest/middleware/basicauth.go (100%) rename src/{internal => ui}/rest/middleware/recovery.go (100%) rename src/{internal => ui}/rest/newsletter.go (90%) rename src/{internal => ui}/rest/send.go (96%) rename src/{internal => ui}/rest/user.go (95%) rename src/{internal => ui}/websocket/websocket.go (97%) rename src/{services => usecase}/app.go (99%) rename src/{services => usecase}/group.go (89%) rename src/{services => usecase}/message.go (98%) rename src/{services => usecase}/newsletter.go (72%) rename src/{services => usecase}/send.go (99%) rename src/{services => usecase}/user.go (88%) diff --git a/.cursor/rules/project-structure.mdc b/.cursor/rules/project-structure.mdc new file mode 100644 index 0000000..5af0483 --- /dev/null +++ b/.cursor/rules/project-structure.mdc @@ -0,0 +1,122 @@ +--- +description: +globs: +alwaysApply: false +--- +# Go WhatsApp Web Multidevice API + +This is a Go implementation of a WhatsApp Web Multidevice API that allows you to interact with WhatsApp through HTTP APIs. + +## Project Structure + +### Root Directory + +- [readme.md](mdc:readme.md) - Project documentation and usage instructions +- [docker-compose.yml](mdc:docker-compose.yml) - Docker configuration for running the application +- [LICENCE.txt](mdc:LICENCE.txt) - License information + +### Source Code (`src/`) + +The main source code is organized in the `src` directory with the following structure: + +#### Command Line Interface + +- [src/cmd/root.go](mdc:src/cmd/root.go) - Main entry point using Cobra for CLI commands, handles configuration loading and server initialization + +#### Configuration + +- [src/config/](mdc:src/config) - Application configuration settings and constants + +#### Domain Models + +The application is organized using domain-driven design principles: + +- [src/domains/app/](mdc:src/domains/app) - Core application domain models +- [src/domains/group/](mdc:src/domains/group) - Group-related domain models +- [src/domains/message/](mdc:src/domains/message) - Message-related domain models +- [src/domains/newsletter/](mdc:src/domains/newsletter) - Newsletter-related domain models +- [src/domains/send/](mdc:src/domains/send) - Message sending domain models +- [src/domains/user/](mdc:src/domains/user) - User-related domain models + +#### Infrastructure + +- [src/infrastructure/whatsapp/](mdc:src/infrastructure/whatsapp) - WhatsApp client implementation and related infrastructure + +#### User Interface + +- [src/ui/rest/](mdc:src/ui/rest) - REST API implementation + - [src/ui/rest/helpers/](mdc:src/ui/rest/helpers) - Helper functions for REST handlers + - [src/ui/rest/middleware/](mdc:src/ui/rest/middleware) - Middleware components for request processing +- [src/ui/websocket/](mdc:src/ui/websocket) - WebSocket implementation for real-time communication +- [src/ui/mcp/](mdc:src/ui/mcp) - Model Context Protocol server to communication with AI Agent + +#### Utilities and Shared Components + +- [src/pkg/error/](mdc:src/pkg/error) - Error handling utilities +- [src/pkg/utils/](mdc:src/pkg/utils) - General utility functions + +#### Use Cases + +- [src/usecase/](mdc:src/usecase) - Application services that implement business logic + +#### Static Resources + +- [src/statics/](mdc:src/statics) - Static resources like media files + - [src/statics/media/](mdc:src/statics/media) - Media files + - [src/statics/qrcode/](mdc:src/statics/qrcode) - QR code images for WhatsApp authentication + - [src/statics/senditems/](mdc:src/statics/senditems) - Items to be sent via WhatsApp + +#### Storage + +- [src/storages/](mdc:src/storages) - Storage-related functionality and database connection + +#### Temporary Files + +- [src/tmp/](mdc:src/tmp) - Temporary files and directories + +#### Validation + +- [src/validations/](mdc:src/validations) - Request validation logic + +#### Views + +- [src/views/](mdc:src/views) - Templates and UI components + - [src/views/assets/](mdc:src/views/assets) - Frontend assets (CSS, JS, etc.) + - [src/views/components/](mdc:src/views/components) - Reusable UI components + - [src/views/components/generic/](mdc:src/views/components/generic) - Generic UI components + +### Documentation + +- [docs/](mdc:docs) - Project documentation + - [docs/sdk/](mdc:docs/sdk) - SDK documentation + +### Docker + +- [docker/](mdc:docker) - Docker-related files and configurations + +### GitHub Configuration + +- [.github/](mdc:.github) - GitHub-specific configuration + - [.github/ISSUE_TEMPLATE/](mdc:.github/ISSUE_TEMPLATE) - Templates for GitHub issues + - [.github/workflows/](mdc:.github/workflows) - GitHub Actions workflows + +## Key Application Features + +- WhatsApp login via QR code or pairing code +- Send/receive messages, media, contacts, locations +- Group management features +- Newsletter management +- WebSocket real-time updates +- Webhooks for message events +- Auto-reply functionality + +## Application Flow + +1. The application starts from [src/cmd/root.go](mdc:src/cmd/root.go) +2. Configuration is loaded from environment variables or command line flags +3. The REST server is initialized using Fiber framework +4. WhatsApp client is initialized and services are created +5. REST routes are registered for different domains +6. WebSocket hub is started for real-time communication +7. Background tasks are started (auto-reconnect, chat storage flushing) +8. The server listens for requests on the configured port diff --git a/readme.md b/readme.md index 7c25343..e8cfbfd 100644 --- a/readme.md +++ b/readme.md @@ -13,16 +13,17 @@ ___ ![release linux](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/release-linux.yml/badge.svg) ![release macos](https://github.com/aldinokemal/go-whatsapp-web-multidevice/actions/workflows/release-mac.yml/badge.svg) -## Support `ARM` Architecture +## Support for `ARM` & `AMD` Architecture along with `MCP` Support -Now that we support ARM64 for Linux: +Download: -- [Release](https://github.com/aldinokemal/go-whatsapp-web-multidevice/releases/latest) for ARM64 -- [Docker Image](https://hub.docker.com/r/aldinokemal2104/go-whatsapp-web-multidevice/tags) for ARM64. +- [Release](https://github.com/aldinokemal/go-whatsapp-web-multidevice/releases/latest) +- [Docker Image](https://hub.docker.com/r/aldinokemal2104/go-whatsapp-web-multidevice/tags) ## Feature - Send WhatsApp message via http API, [docs/openapi.yml](./docs/openapi.yaml) for more details +- **MCP (Model Context Protocol) Server Support** - Integrate with AI agents and tools using standardized protocol - Mention someone - `@phoneNumber` - example: `Hello @628974812XXXX, @628974812XXXX` @@ -115,10 +116,50 @@ Note: Command-line flags will override any values set in environment variables o 1. run `.\whatsapp.exe --help` for more detail flags 6. open `http://localhost:3000` in browser +### MCP Server (Model Context Protocol) + +This application can also run as an MCP server, allowing AI agents and tools to interact with WhatsApp through a standardized protocol. + +1. Clone this repo `git clone https://github.com/aldinokemal/go-whatsapp-web-multidevice` +2. Open the folder that was cloned via cmd/terminal. +3. run `cd src` +4. run `go run main.go mcp` or build the binary and run `./whatsapp mcp` +5. The MCP server will start on `http://localhost:8080` by default + +#### MCP Server Options + +- `--host localhost` - Set the host for MCP server (default: localhost) +- `--port 8080` - Set the port for MCP server (default: 8080) + +#### Available MCP Tools + +- `whatsapp_send_text` - Send text messages +- `whatsapp_send_contact` - Send contact cards +- `whatsapp_send_link` - Send links with captions +- `whatsapp_send_location` - Send location coordinates + +#### MCP Endpoints + +- SSE endpoint: `http://localhost:8080/sse` +- Message endpoint: `http://localhost:8080/message` + +### MCP Configuration + +make sure you have running MCP server, `./whatsapp mcp` + +```json +{ + "mcpServers": { + "whatsapp": { + "url": "http://localhost:8080/sse" + } + } +``` + ### Production Mode (docker) ```bash -docker run --detach --publish=3000:3000 --name=whatsapp --restart=always --volume=$(docker volume create --name=whatsapp):/app/storages aldinokemal2104/go-whatsapp-web-multidevice --autoreply="Dont't reply this message please" +docker run --detach --publish=3000:3000 --name=whatsapp --restart=always --volume=$(docker volume create --name=whatsapp):/app/storages aldinokemal2104/go-whatsapp-web-multidevice rest --autoreply="Dont't reply this message please" ``` ### Production Mode (docker compose) @@ -136,6 +177,7 @@ services: volumes: - whatsapp:/app/storages command: + - rest - --basic-auth=admin:admin - --port=3000 - --debug=true @@ -158,6 +200,8 @@ services: - "3000:3000" volumes: - whatsapp:/app/storages + command: + - rest environment: - APP_BASIC_AUTH=admin:admin - APP_PORT=3000 @@ -177,6 +221,15 @@ You can fork or edit this source code ! ## Current API +### MCP (Model Context Protocol) API + +- MCP server provides standardized tools for AI agents to interact with WhatsApp +- Supports Server-Sent Events (SSE) transport +- Available tools: `whatsapp_send_text`, `whatsapp_send_contact`, `whatsapp_send_link`, `whatsapp_send_location` +- Compatible with MCP-enabled AI tools and agents + +### HTTP REST API + - [API Specification Document](https://bump.sh/aldinokemal/doc/go-whatsapp-web-multidevice). - Check [docs/openapi.yml](./docs/openapi.yaml) for detailed API specifications. - Use [SwaggerEditor](https://editor.swagger.io) to visualize the API. @@ -230,7 +283,18 @@ You can fork or edit this source code ! ❌ = Not Available Yet ``` -### User Interface +## User Interface + +### MCP UI + +- Setup MCP (tested in cursor) +![Setup MCP](https://i.ibb.co/vCg4zNWt/mcpsetup.png) +- Test MCP +![Test MCP](https://i.ibb.co/B2LX38DW/mcptest.png) +- Successfully setup MCP +![Success MCP](https://i.ibb.co/1fCx0Myc/mcpsuccess.png) + +### HTTP REST API UI | Description | Image | |----------------------|------------------------------------------------------------------------------------------| @@ -244,7 +308,7 @@ You can fork or edit this source code ! | Send Contact | ![Send Contact](https://i.ibb.co.com/NsFfQBv/send-Contact.png) | | Send Location | ![Send Location](https://i.ibb.co.com/vDGmFvk/send-Location.png) | | Send Audio | ![Send Audio](https://i.ibb.co.com/XJdQLP8/send-Audio.png) | -| Send Poll | ![Send Poll](https://i.ibb.co.com/4TswfT3/sendPoll.png) | +| Send Poll | ![Send Poll](https://i.ibb.co.com/4TswfT3/sendPoll.png?v=1) | | Send Presence | ![Send Presence](https://i.ibb.co.com/NSTC3QX/send-Presence.png) | | Revoke Message | ![Revoke Message](https://i.ibb.co.com/r4nDc57/revoke-Message.png) | | Delete Message | ![Delete Message](https://i.ibb.co.com/dtrTJ1M/delete-Message.png) | @@ -270,3 +334,4 @@ You can fork or edit this source code ! - This project is unofficial and not affiliated with WhatsApp. - Please use official WhatsApp API to avoid any issues. +- We only able to run MCP or REST API, this is limitation from whatsmeow library. independent MCP will be available in the future. diff --git a/src/cmd/mcp.go b/src/cmd/mcp.go new file mode 100644 index 0000000..8bfd1af --- /dev/null +++ b/src/cmd/mcp.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "fmt" + "log" + + "github.com/aldinokemal/go-whatsapp-web-multidevice/config" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/mcp" + "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/rest/helpers" + "github.com/mark3labs/mcp-go/server" + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var mcpCmd = &cobra.Command{ + Use: "mcp", + Short: "Start WhatsApp MCP server using SSE", + Long: `Start a WhatsApp MCP (Model Context Protocol) server using Server-Sent Events (SSE) transport. This allows AI agents to interact with WhatsApp through a standardized protocol.`, + Run: mcpServer, +} + +func init() { + rootCmd.AddCommand(mcpCmd) + mcpCmd.Flags().StringVar(&config.McpPort, "port", "8080", "Port for the SSE MCP server") + mcpCmd.Flags().StringVar(&config.McpHost, "host", "localhost", "Host for the SSE MCP server") +} + +func mcpServer(_ *cobra.Command, _ []string) { + // Prepare folders if not exist + err := utils.CreateFolder(config.PathQrCode, config.PathSendItems, config.PathStorages, config.PathMedia) + if err != nil { + log.Fatalf("Failed to create folders: %v", err) + } + + // Set auto reconnect to whatsapp server after booting + go helpers.SetAutoConnectAfterBooting(appUsecase) + // Set auto reconnect checking + go helpers.SetAutoReconnectChecking(whatsappCli) + + // Create MCP server with capabilities + mcpServer := server.NewMCPServer( + "WhatsApp Web Multidevice MCP Server", + config.AppVersion, + server.WithToolCapabilities(true), + server.WithResourceCapabilities(true, true), + ) + + // Add all WhatsApp tools + sendHandler := mcp.InitMcpSend(sendUsecase) + sendHandler.AddSendTools(mcpServer) + + // Create SSE server + sseServer := server.NewSSEServer( + mcpServer, + server.WithBaseURL(fmt.Sprintf("http://%s:%s", config.McpHost, config.McpPort)), + server.WithKeepAlive(true), + ) + + // Start the SSE server + addr := fmt.Sprintf("%s:%s", config.McpHost, config.McpPort) + log.Printf("Starting WhatsApp MCP SSE server on %s", addr) + log.Printf("SSE endpoint: http://%s:%s/sse", config.McpHost, config.McpPort) + log.Printf("Message endpoint: http://%s:%s/message", config.McpHost, config.McpPort) + + if err := sseServer.Start(addr); err != nil { + log.Fatalf("Failed to start SSE server: %v", err) + } +} diff --git a/src/cmd/rest.go b/src/cmd/rest.go new file mode 100644 index 0000000..b2c2dfc --- /dev/null +++ b/src/cmd/rest.go @@ -0,0 +1,122 @@ +package cmd + +import ( + "fmt" + "log" + "net/http" + "strings" + + "github.com/aldinokemal/go-whatsapp-web-multidevice/config" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/rest" + "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/rest/helpers" + "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/rest/middleware" + "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/websocket" + "github.com/dustin/go-humanize" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/basicauth" + "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v2/middleware/filesystem" + "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/template/html/v2" + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var restCmd = &cobra.Command{ + Use: "rest", + Short: "Send whatsapp API over http", + Long: `This application is from clone https://github.com/aldinokemal/go-whatsapp-web-multidevice`, + Run: restServer, +} + +func init() { + rootCmd.AddCommand(restCmd) +} +func restServer(_ *cobra.Command, _ []string) { + //preparing folder if not exist + err := utils.CreateFolder(config.PathQrCode, config.PathSendItems, config.PathStorages, config.PathMedia) + if err != nil { + log.Fatalln(err) + } + + engine := html.NewFileSystem(http.FS(EmbedIndex), ".html") + engine.AddFunc("isEnableBasicAuth", func(token any) bool { + return token != nil + }) + app := fiber.New(fiber.Config{ + Views: engine, + BodyLimit: int(config.WhatsappSettingMaxVideoSize), + }) + + app.Static("/statics", "./statics") + app.Use("/components", filesystem.New(filesystem.Config{ + Root: http.FS(EmbedViews), + PathPrefix: "views/components", + Browse: true, + })) + app.Use("/assets", filesystem.New(filesystem.Config{ + Root: http.FS(EmbedViews), + PathPrefix: "views/assets", + Browse: true, + })) + + app.Use(middleware.Recovery()) + app.Use(middleware.BasicAuth()) + if config.AppDebug { + app.Use(logger.New()) + } + app.Use(cors.New(cors.Config{ + AllowOrigins: "*", + AllowHeaders: "Origin, Content-Type, Accept", + })) + + if len(config.AppBasicAuthCredential) > 0 { + account := make(map[string]string) + for _, basicAuth := range config.AppBasicAuthCredential { + ba := strings.Split(basicAuth, ":") + if len(ba) != 2 { + log.Fatalln("Basic auth is not valid, please this following format :") + } + account[ba[0]] = ba[1] + } + + app.Use(basicauth.New(basicauth.Config{ + Users: account, + })) + } + + // Rest + rest.InitRestApp(app, appUsecase) + rest.InitRestSend(app, sendUsecase) + rest.InitRestUser(app, userUsecase) + rest.InitRestMessage(app, messageUsecase) + rest.InitRestGroup(app, groupUsecase) + rest.InitRestNewsletter(app, newsletterUsecase) + + app.Get("/", func(c *fiber.Ctx) error { + return c.Render("views/index", fiber.Map{ + "AppHost": fmt.Sprintf("%s://%s", c.Protocol(), c.Hostname()), + "AppVersion": config.AppVersion, + "BasicAuthToken": c.UserContext().Value(middleware.AuthorizationValue("BASIC_AUTH")), + "MaxFileSize": humanize.Bytes(uint64(config.WhatsappSettingMaxFileSize)), + "MaxVideoSize": humanize.Bytes(uint64(config.WhatsappSettingMaxVideoSize)), + }) + }) + + websocket.RegisterRoutes(app, appUsecase) + go websocket.RunHub() + + // Set auto reconnect to whatsapp server after booting + go helpers.SetAutoConnectAfterBooting(appUsecase) + // Set auto reconnect checking + go helpers.SetAutoReconnectChecking(whatsappCli) + // Start auto flush chat csv + if config.WhatsappChatStorage { + go helpers.StartAutoFlushChatStorage() + } + + if err = app.Listen(":" + config.AppPort); err != nil { + log.Fatalln("Failed to start: ", err.Error()) + } +} diff --git a/src/cmd/root.go b/src/cmd/root.go index 750ac5f..389ad20 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -3,36 +3,44 @@ package cmd import ( "context" "embed" - "fmt" - "log" - "net/http" "os" "strings" + "time" "github.com/aldinokemal/go-whatsapp-web-multidevice/config" - "github.com/aldinokemal/go-whatsapp-web-multidevice/internal/rest" - "github.com/aldinokemal/go-whatsapp-web-multidevice/internal/rest/helpers" - "github.com/aldinokemal/go-whatsapp-web-multidevice/internal/rest/middleware" - "github.com/aldinokemal/go-whatsapp-web-multidevice/internal/websocket" + domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" + domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group" + domainMessage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" + domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newsletter" + domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" + domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" + "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/pkg/whatsapp" - "github.com/aldinokemal/go-whatsapp-web-multidevice/services" - "github.com/dustin/go-humanize" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/basicauth" - "github.com/gofiber/fiber/v2/middleware/cors" - "github.com/gofiber/fiber/v2/middleware/filesystem" - "github.com/gofiber/fiber/v2/middleware/logger" - "github.com/gofiber/template/html/v2" - _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" + "github.com/aldinokemal/go-whatsapp-web-multidevice/usecase" "github.com/spf13/cobra" "github.com/spf13/viper" + "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/store/sqlstore" + + _ "github.com/lib/pq" + _ "github.com/mattn/go-sqlite3" ) var ( EmbedIndex embed.FS EmbedViews embed.FS + + // Whatsapp + whatsappCli *whatsmeow.Client + whatsappDB *sqlstore.Container + + // Usecase + appUsecase domainApp.IAppUsecase + sendUsecase domainSend.ISendUsecase + userUsecase domainUser.IUserUsecase + messageUsecase domainMessage.IMessageUsecase + groupUsecase domainGroup.IGroupUsecase + newsletterUsecase domainNewsletter.INewsletterUsecase ) // rootCmd represents the base command when called without any subcommands @@ -40,16 +48,21 @@ var rootCmd = &cobra.Command{ Short: "Send free whatsapp API", Long: `This application is from clone https://github.com/aldinokemal/go-whatsapp-web-multidevice, you can send whatsapp over http api but your whatsapp account have to be multi device version`, - Run: runRest, } func init() { // Load environment variables first utils.LoadConfig(".") - // Initialize configurations, flag is higher priority than env - initEnvConfig() + time.Local = time.UTC + + rootCmd.CompletionOptions.DisableDefaultCmd = true + + // Initialize flags first, before any subcommands are added initFlags() + + // Then initialize other components + cobra.OnInitialize(initEnvConfig, initApp) } // initEnvConfig loads configuration from environment variables @@ -96,10 +109,7 @@ func initEnvConfig() { } } -// initFlags sets up command line flags that override environment variables func initFlags() { - rootCmd.CompletionOptions.DisableDefaultCmd = true - // Application flags rootCmd.PersistentFlags().StringVarP( &config.AppPort, @@ -107,6 +117,7 @@ func initFlags() { config.AppPort, "change port number with --port | example: --port=8080", ) + rootCmd.PersistentFlags().BoolVarP( &config.AppDebug, "debug", "d", @@ -173,109 +184,22 @@ func initFlags() { ) } -func runRest(_ *cobra.Command, _ []string) { +func initApp() { if config.AppDebug { config.WhatsappLogLevel = "DEBUG" } - // TODO: Init Rest App - //preparing folder if not exist - err := utils.CreateFolder(config.PathQrCode, config.PathSendItems, config.PathStorages, config.PathMedia) - if err != nil { - log.Fatalln(err) - } - - engine := html.NewFileSystem(http.FS(EmbedIndex), ".html") - engine.AddFunc("isEnableBasicAuth", func(token any) bool { - return token != nil - }) - app := fiber.New(fiber.Config{ - Views: engine, - BodyLimit: int(config.WhatsappSettingMaxVideoSize), - }) - - app.Static("/statics", "./statics") - app.Use("/components", filesystem.New(filesystem.Config{ - Root: http.FS(EmbedViews), - PathPrefix: "views/components", - Browse: true, - })) - app.Use("/assets", filesystem.New(filesystem.Config{ - Root: http.FS(EmbedViews), - PathPrefix: "views/assets", - Browse: true, - })) - - app.Use(middleware.Recovery()) - app.Use(middleware.BasicAuth()) - if config.AppDebug { - app.Use(logger.New()) - } - app.Use(cors.New(cors.Config{ - AllowOrigins: "*", - AllowHeaders: "Origin, Content-Type, Accept", - })) - - if len(config.AppBasicAuthCredential) > 0 { - account := make(map[string]string) - for _, basicAuth := range config.AppBasicAuthCredential { - ba := strings.Split(basicAuth, ":") - if len(ba) != 2 { - log.Fatalln("Basic auth is not valid, please this following format :") - } - account[ba[0]] = ba[1] - } - - app.Use(basicauth.New(basicauth.Config{ - Users: account, - })) - } - ctx := context.Background() - db := whatsapp.InitWaDB(ctx) - cli := whatsapp.InitWaCLI(ctx, db) - - // Service - appService := services.NewAppService(cli, db) - sendService := services.NewSendService(cli, appService) - userService := services.NewUserService(cli) - messageService := services.NewMessageService(cli) - groupService := services.NewGroupService(cli) - newsletterService := services.NewNewsletterService(cli) - - // Rest - rest.InitRestApp(app, appService) - rest.InitRestSend(app, sendService) - rest.InitRestUser(app, userService) - rest.InitRestMessage(app, messageService) - rest.InitRestGroup(app, groupService) - rest.InitRestNewsletter(app, newsletterService) - - app.Get("/", func(c *fiber.Ctx) error { - return c.Render("views/index", fiber.Map{ - "AppHost": fmt.Sprintf("%s://%s", c.Protocol(), c.Hostname()), - "AppVersion": config.AppVersion, - "BasicAuthToken": c.UserContext().Value(middleware.AuthorizationValue("BASIC_AUTH")), - "MaxFileSize": humanize.Bytes(uint64(config.WhatsappSettingMaxFileSize)), - "MaxVideoSize": humanize.Bytes(uint64(config.WhatsappSettingMaxVideoSize)), - }) - }) - - websocket.RegisterRoutes(app, appService) - go websocket.RunHub() - - // Set auto reconnect to whatsapp server after booting - go helpers.SetAutoConnectAfterBooting(appService) - // Set auto reconnect checking - go helpers.SetAutoReconnectChecking(cli) - // Start auto flush chat csv - if config.WhatsappChatStorage { - go helpers.StartAutoFlushChatStorage() - } - - if err = app.Listen(":" + config.AppPort); err != nil { - log.Fatalln("Failed to start: ", err.Error()) - } + whatsappDB = whatsapp.InitWaDB(ctx) + whatsappCli = whatsapp.InitWaCLI(ctx, whatsappDB) + + // Usecase + appUsecase = usecase.NewAppService(whatsappCli, whatsappDB) + sendUsecase = usecase.NewSendService(whatsappCli, appUsecase) + userUsecase = usecase.NewUserService(whatsappCli) + messageUsecase = usecase.NewMessageService(whatsappCli) + groupUsecase = usecase.NewGroupService(whatsappCli) + newsletterUsecase = usecase.NewNewsletterService(whatsappCli) } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/src/config/settings.go b/src/config/settings.go index 7c967d9..d4e9979 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -5,7 +5,7 @@ import ( ) var ( - AppVersion = "v5.6.2" + AppVersion = "v6.0.0" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" @@ -13,6 +13,9 @@ var ( AppBasicAuthCredential []string AppChatFlushIntervalDays = 7 // Number of days before flushing chat.csv + McpPort = "8080" + McpHost = "localhost" + PathQrCode = "statics/qrcode" PathSendItems = "statics/senditems" PathMedia = "statics/media" diff --git a/src/domains/app/app.go b/src/domains/app/app.go index dbe3bb3..6325ed7 100644 --- a/src/domains/app/app.go +++ b/src/domains/app/app.go @@ -5,7 +5,7 @@ import ( "time" ) -type IAppService interface { +type IAppUsecase interface { Login(ctx context.Context) (response LoginResponse, err error) LoginWithCode(ctx context.Context, phoneNumber string) (loginCode string, err error) Logout(ctx context.Context) (err error) diff --git a/src/domains/group/group.go b/src/domains/group/group.go index 36e8db4..b6fd437 100644 --- a/src/domains/group/group.go +++ b/src/domains/group/group.go @@ -7,7 +7,7 @@ import ( "go.mau.fi/whatsmeow" ) -type IGroupService interface { +type IGroupUsecase interface { JoinGroupWithLink(ctx context.Context, request JoinGroupWithLinkRequest) (groupID string, err error) LeaveGroup(ctx context.Context, request LeaveGroupRequest) (err error) CreateGroup(ctx context.Context, request CreateGroupRequest) (groupID string, err error) diff --git a/src/domains/message/message.go b/src/domains/message/message.go index a2db0ad..6a0f445 100644 --- a/src/domains/message/message.go +++ b/src/domains/message/message.go @@ -2,7 +2,7 @@ package message import "context" -type IMessageService interface { +type IMessageUsecase interface { MarkAsRead(ctx context.Context, request MarkAsReadRequest) (response GenericResponse, err error) ReactMessage(ctx context.Context, request ReactionRequest) (response GenericResponse, err error) RevokeMessage(ctx context.Context, request RevokeRequest) (response GenericResponse, err error) diff --git a/src/domains/newsletter/newsletter.go b/src/domains/newsletter/newsletter.go index 16b1452..05a1f7c 100644 --- a/src/domains/newsletter/newsletter.go +++ b/src/domains/newsletter/newsletter.go @@ -2,7 +2,7 @@ package newsletter import "context" -type INewsletterService interface { +type INewsletterUsecase interface { Unfollow(ctx context.Context, request UnfollowRequest) (err error) } diff --git a/src/domains/send/send.go b/src/domains/send/send.go index 9e0be5e..643f933 100644 --- a/src/domains/send/send.go +++ b/src/domains/send/send.go @@ -4,7 +4,7 @@ import ( "context" ) -type ISendService interface { +type ISendUsecase interface { SendText(ctx context.Context, request MessageRequest) (response GenericResponse, err error) SendImage(ctx context.Context, request ImageRequest) (response GenericResponse, err error) SendFile(ctx context.Context, request FileRequest) (response GenericResponse, err error) diff --git a/src/domains/user/user.go b/src/domains/user/user.go index ef78619..902fb52 100644 --- a/src/domains/user/user.go +++ b/src/domains/user/user.go @@ -4,7 +4,7 @@ import ( "context" ) -type IUserService interface { +type IUserUsecase interface { Info(ctx context.Context, request InfoRequest) (response InfoResponse, err error) Avatar(ctx context.Context, request AvatarRequest) (response AvatarResponse, err error) ChangeAvatar(ctx context.Context, request ChangeAvatarRequest) (err error) diff --git a/src/go.mod b/src/go.mod index c9ce1a2..7e25a4d 100644 --- a/src/go.mod +++ b/src/go.mod @@ -12,6 +12,7 @@ require ( github.com/gofiber/websocket/v2 v2.2.1 github.com/google/uuid v1.6.0 github.com/lib/pq v1.10.9 + github.com/mark3labs/mcp-go v0.29.0 github.com/mattn/go-sqlite3 v1.14.28 github.com/sirupsen/logrus v1.9.3 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e @@ -30,6 +31,8 @@ require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fasthttp/websocket v1.5.12 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect @@ -38,12 +41,16 @@ require ( github.com/gofiber/utils v1.1.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/jsonschema v0.12.0 // indirect github.com/klauspost/compress v1.18.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/metoro-io/mcp-golang v0.13.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/zerolog v1.34.0 // indirect @@ -54,7 +61,13 @@ require ( github.com/spf13/cast v1.8.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.mau.fi/util v0.8.7 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.38.0 // indirect diff --git a/src/go.sum b/src/go.sum index 7f9a58b..a57439f 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,7 +1,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= -github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= @@ -11,6 +11,10 @@ github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmg github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -25,8 +29,6 @@ github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOU github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= @@ -34,8 +36,6 @@ github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRi github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= -github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4= github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= @@ -54,6 +54,9 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= +github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -62,6 +65,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mark3labs/mcp-go v0.29.0 h1:sH1NBcumKskhxqYzhXfGc201D7P76TVXiT0fGVhabeI= +github.com/mark3labs/mcp-go v0.29.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -71,20 +78,15 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU= -github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/metoro-io/mcp-golang v0.13.0 h1:54TFBJIW76VRB55CJovQQje9x4GnXg0BQQwGRtXrbCE= +github.com/metoro-io/mcp-golang v0.13.0/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a h1:S+AGcmAESQ0pXCUNnRH7V+bOUIgkSX5qVt2cNKCrm0Q= -github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb h1:3PrKuO92dUTMrQ9dx0YNejC6U/Si6jqKmyQ9vWjwqR4= github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -98,12 +100,8 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.8.0 h1:mXaMVw7IqxNBxfv3LdWt9MDmcWDQ1fagDH918lOdVaQ= -github.com/sagikazarmark/locafero v0.8.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= -github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= -github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4= github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -114,16 +112,12 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= -github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -133,33 +127,31 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= -github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= -github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw= -github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc= github.com/valyala/fasthttp v1.62.0 h1:8dKRBX/y2rCzyc6903Zu1+3qN0H/d2MsxPPmVNamiH0= github.com/valyala/fasthttp v1.62.0/go.mod h1:FCINgr4GKdKqV8Q0xv8b+UxPV+H/O5nNFo3D+r54Htg= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= -go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= -go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= -go.mau.fi/whatsmeow v0.0.0-20250318233852-06705625cf82 h1:AZlDkXHgoQNW4gd2hnTCvPH7hYznmwc3gPaYqGZ5w8A= -go.mau.fi/whatsmeow v0.0.0-20250318233852-06705625cf82/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs= -go.mau.fi/whatsmeow v0.0.0-20250402091807-b0caa1b76088 h1:ns6nk2NjqdaQnCKrp+Qqwpf+3OI7+nnH56D71+7XzOM= -go.mau.fi/whatsmeow v0.0.0-20250402091807-b0caa1b76088/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs= -go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526 h1:i9w16FdM3zmOWdF5nh1l2MlmE/wK7ulL6rbT02WBBJs= -go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= -go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa h1:+bQKfMtnhX2jVoCSaneH4Ctk51IVT1K2gvjyqfFjVW0= -go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= go.mau.fi/whatsmeow v0.0.0-20250521125706-91ac75c2f61a h1:PYtzz5wdma64I47CiquGicyubzg3HIPkH/jMzpmHu8g= go.mau.fi/whatsmeow v0.0.0-20250521125706-91ac75c2f61a/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -170,21 +162,11 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= -golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= -golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= -golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -201,12 +183,6 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -230,10 +206,6 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= @@ -254,10 +226,6 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -267,8 +235,6 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/src/pkg/whatsapp/init.go b/src/infrastructure/whatsapp/init.go similarity index 99% rename from src/pkg/whatsapp/init.go rename to src/infrastructure/whatsapp/init.go index 3f28c1a..1190798 100644 --- a/src/pkg/whatsapp/init.go +++ b/src/infrastructure/whatsapp/init.go @@ -10,9 +10,9 @@ import ( "time" "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/aldinokemal/go-whatsapp-web-multidevice/ui/websocket" "github.com/sirupsen/logrus" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" diff --git a/src/pkg/whatsapp/utils.go b/src/infrastructure/whatsapp/utils.go similarity index 100% rename from src/pkg/whatsapp/utils.go rename to src/infrastructure/whatsapp/utils.go diff --git a/src/pkg/whatsapp/webhook.go b/src/infrastructure/whatsapp/webhook.go similarity index 100% rename from src/pkg/whatsapp/webhook.go rename to src/infrastructure/whatsapp/webhook.go diff --git a/src/ui/mcp/send.go b/src/ui/mcp/send.go new file mode 100644 index 0000000..6f7edfc --- /dev/null +++ b/src/ui/mcp/send.go @@ -0,0 +1,338 @@ +package mcp + +import ( + "context" + "errors" + "fmt" + + domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" + "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" +) + +type SendHandler struct { + sendService domainSend.ISendUsecase +} + +func InitMcpSend(sendService domainSend.ISendUsecase) *SendHandler { + return &SendHandler{ + sendService: sendService, + } +} + +func (s *SendHandler) AddSendTools(mcpServer *server.MCPServer) { + mcpServer.AddTool(s.toolSendText(), s.handleSendText) + mcpServer.AddTool(s.toolSendContact(), s.handleSendContact) + mcpServer.AddTool(s.toolSendLink(), s.handleSendLink) + mcpServer.AddTool(s.toolSendLocation(), s.handleSendLocation) + mcpServer.AddTool(s.toolSendImage(), s.handleSendImage) +} + +func (s *SendHandler) toolSendText() mcp.Tool { + sendTextTool := mcp.NewTool("whatsapp_send_text", + mcp.WithDescription("Send a text message to a WhatsApp contact or group."), + mcp.WithString("phone", + mcp.Required(), + mcp.Description("Phone number or group ID to send message to"), + ), + mcp.WithString("message", + mcp.Required(), + mcp.Description("The text message to send"), + ), + mcp.WithBoolean("is_forwarded", + mcp.Description("Whether this message is being forwarded (default: false)"), + ), + mcp.WithString("reply_message_id", + mcp.Description("Message ID to reply to (optional)"), + ), + ) + + return sendTextTool +} + +func (s *SendHandler) handleSendText(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + phone, ok := request.GetArguments()["phone"].(string) + if !ok { + return nil, errors.New("phone must be a string") + } + + message, ok := request.GetArguments()["message"].(string) + if !ok { + return nil, errors.New("message must be a string") + } + + isForwarded, ok := request.GetArguments()["is_forwarded"].(bool) + if !ok { + isForwarded = false + } + + replyMessageId, ok := request.GetArguments()["reply_message_id"].(string) + if !ok { + replyMessageId = "" + } + + res, err := s.sendService.SendText(ctx, domainSend.MessageRequest{ + Phone: phone, + Message: message, + IsForwarded: isForwarded, + ReplyMessageID: &replyMessageId, + }) + + if err != nil { + return nil, err + } + + return mcp.NewToolResultText(fmt.Sprintf("Message sent successfully with ID %s", res.MessageID)), nil +} + +func (s *SendHandler) toolSendContact() mcp.Tool { + sendContactTool := mcp.NewTool("whatsapp_send_contact", + mcp.WithDescription("Send a contact card to a WhatsApp contact or group."), + mcp.WithString("phone", + mcp.Required(), + mcp.Description("Phone number or group ID to send contact to"), + ), + mcp.WithString("contact_name", + mcp.Required(), + mcp.Description("Name of the contact to send"), + ), + mcp.WithString("contact_phone", + mcp.Required(), + mcp.Description("Phone number of the contact to send"), + ), + mcp.WithBoolean("is_forwarded", + mcp.Description("Whether this message is being forwarded (default: false)"), + ), + ) + + return sendContactTool +} + +func (s *SendHandler) handleSendContact(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + phone, ok := request.GetArguments()["phone"].(string) + if !ok { + return nil, errors.New("phone must be a string") + } + + contactName, ok := request.GetArguments()["contact_name"].(string) + if !ok { + return nil, errors.New("contact_name must be a string") + } + + contactPhone, ok := request.GetArguments()["contact_phone"].(string) + if !ok { + return nil, errors.New("contact_phone must be a string") + } + + isForwarded, ok := request.GetArguments()["is_forwarded"].(bool) + if !ok { + isForwarded = false + } + + res, err := s.sendService.SendContact(ctx, domainSend.ContactRequest{ + Phone: phone, + ContactName: contactName, + ContactPhone: contactPhone, + IsForwarded: isForwarded, + }) + + if err != nil { + return nil, err + } + + return mcp.NewToolResultText(fmt.Sprintf("Contact sent successfully with ID %s", res.MessageID)), nil +} + +func (s *SendHandler) toolSendLink() mcp.Tool { + sendLinkTool := mcp.NewTool("whatsapp_send_link", + mcp.WithDescription("Send a link with caption to a WhatsApp contact or group."), + mcp.WithString("phone", + mcp.Required(), + mcp.Description("Phone number or group ID to send link to"), + ), + mcp.WithString("link", + mcp.Required(), + mcp.Description("URL link to send"), + ), + mcp.WithString("caption", + mcp.Required(), + mcp.Description("Caption or description for the link"), + ), + mcp.WithBoolean("is_forwarded", + mcp.Description("Whether this message is being forwarded (default: false)"), + ), + ) + + return sendLinkTool +} + +func (s *SendHandler) handleSendLink(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + phone, ok := request.GetArguments()["phone"].(string) + if !ok { + return nil, errors.New("phone must be a string") + } + + link, ok := request.GetArguments()["link"].(string) + if !ok { + return nil, errors.New("link must be a string") + } + + caption, ok := request.GetArguments()["caption"].(string) + if !ok { + caption = "" + } + + isForwarded, ok := request.GetArguments()["is_forwarded"].(bool) + if !ok { + isForwarded = false + } + + res, err := s.sendService.SendLink(ctx, domainSend.LinkRequest{ + Phone: phone, + Link: link, + Caption: caption, + IsForwarded: isForwarded, + }) + + if err != nil { + return nil, err + } + + return mcp.NewToolResultText(fmt.Sprintf("Link sent successfully with ID %s", res.MessageID)), nil +} + +func (s *SendHandler) toolSendLocation() mcp.Tool { + sendLocationTool := mcp.NewTool("whatsapp_send_location", + mcp.WithDescription("Send a location coordinates to a WhatsApp contact or group."), + mcp.WithString("phone", + mcp.Required(), + mcp.Description("Phone number or group ID to send location to"), + ), + mcp.WithString("latitude", + mcp.Required(), + mcp.Description("Latitude coordinate (as string)"), + ), + mcp.WithString("longitude", + mcp.Required(), + mcp.Description("Longitude coordinate (as string)"), + ), + mcp.WithBoolean("is_forwarded", + mcp.Description("Whether this message is being forwarded (default: false)"), + ), + ) + + return sendLocationTool +} + +func (s *SendHandler) handleSendLocation(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + phone, ok := request.GetArguments()["phone"].(string) + if !ok { + return nil, errors.New("phone must be a string") + } + + latitude, ok := request.GetArguments()["latitude"].(string) + if !ok { + return nil, errors.New("latitude must be a string") + } + + longitude, ok := request.GetArguments()["longitude"].(string) + if !ok { + return nil, errors.New("longitude must be a string") + } + + isForwarded, ok := request.GetArguments()["is_forwarded"].(bool) + if !ok { + isForwarded = false + } + + res, err := s.sendService.SendLocation(ctx, domainSend.LocationRequest{ + Phone: phone, + Latitude: latitude, + Longitude: longitude, + IsForwarded: isForwarded, + }) + + if err != nil { + return nil, err + } + + return mcp.NewToolResultText(fmt.Sprintf("Location sent successfully with ID %s", res.MessageID)), nil +} + +func (s *SendHandler) toolSendImage() mcp.Tool { + sendImageTool := mcp.NewTool("whatsapp_send_image", + mcp.WithDescription("Send an image to a WhatsApp contact or group."), + mcp.WithString("phone", + mcp.Required(), + mcp.Description("Phone number or group ID to send image to"), + ), + mcp.WithString("image_url", + mcp.Description("URL of the image to send"), + ), + mcp.WithString("caption", + mcp.Description("Caption or description for the image"), + ), + mcp.WithBoolean("view_once", + mcp.Description("Whether this image should be viewed only once (default: false)"), + ), + mcp.WithBoolean("compress", + mcp.Description("Whether to compress the image (default: true)"), + ), + mcp.WithBoolean("is_forwarded", + mcp.Description("Whether this message is being forwarded (default: false)"), + ), + ) + + return sendImageTool +} + +func (s *SendHandler) handleSendImage(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + phone, ok := request.GetArguments()["phone"].(string) + if !ok { + return nil, errors.New("phone must be a string") + } + + imageURL, imageURLOk := request.GetArguments()["image_url"].(string) + if !imageURLOk { + return nil, errors.New("image_url must be a string") + } + + caption, ok := request.GetArguments()["caption"].(string) + if !ok { + caption = "" + } + + viewOnce, ok := request.GetArguments()["view_once"].(bool) + if !ok { + viewOnce = false + } + + compress, ok := request.GetArguments()["compress"].(bool) + if !ok { + compress = true + } + + isForwarded, ok := request.GetArguments()["is_forwarded"].(bool) + if !ok { + isForwarded = false + } + + // Create image request + imageRequest := domainSend.ImageRequest{ + Phone: phone, + Caption: caption, + ViewOnce: viewOnce, + Compress: compress, + IsForwarded: isForwarded, + } + + if imageURLOk && imageURL != "" { + imageRequest.ImageURL = &imageURL + } + res, err := s.sendService.SendImage(ctx, imageRequest) + if err != nil { + return nil, err + } + + return mcp.NewToolResultText(fmt.Sprintf("Image sent successfully with ID %s", res.MessageID)), nil +} diff --git a/src/internal/rest/app.go b/src/ui/rest/app.go similarity index 95% rename from src/internal/rest/app.go rename to src/ui/rest/app.go index c72cde0..73f3f61 100644 --- a/src/internal/rest/app.go +++ b/src/ui/rest/app.go @@ -2,16 +2,17 @@ package rest import ( "fmt" + domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" "github.com/gofiber/fiber/v2" ) type App struct { - Service domainApp.IAppService + Service domainApp.IAppUsecase } -func InitRestApp(app *fiber.App, service domainApp.IAppService) App { +func InitRestApp(app *fiber.App, service domainApp.IAppUsecase) App { rest := App{Service: service} app.Get("/app/login", rest.Login) app.Get("/app/login-with-code", rest.LoginWithCode) diff --git a/src/internal/rest/group.go b/src/ui/rest/group.go similarity index 96% rename from src/internal/rest/group.go rename to src/ui/rest/group.go index b365862..c56d5ca 100644 --- a/src/internal/rest/group.go +++ b/src/ui/rest/group.go @@ -4,17 +4,17 @@ import ( "fmt" domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group" + "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/pkg/whatsapp" "github.com/gofiber/fiber/v2" "go.mau.fi/whatsmeow" ) type Group struct { - Service domainGroup.IGroupService + Service domainGroup.IGroupUsecase } -func InitRestGroup(app *fiber.App, service domainGroup.IGroupService) Group { +func InitRestGroup(app *fiber.App, service domainGroup.IGroupUsecase) Group { rest := Group{Service: service} app.Post("/group", rest.CreateGroup) app.Post("/group/join-with-link", rest.JoinGroupWithLink) diff --git a/src/internal/rest/helpers/common.go b/src/ui/rest/helpers/common.go similarity index 91% rename from src/internal/rest/helpers/common.go rename to src/ui/rest/helpers/common.go index 0b89a67..ae81d81 100644 --- a/src/internal/rest/helpers/common.go +++ b/src/ui/rest/helpers/common.go @@ -2,13 +2,14 @@ package helpers import ( "context" - domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" - "go.mau.fi/whatsmeow" "mime/multipart" "time" + + domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" + "go.mau.fi/whatsmeow" ) -func SetAutoConnectAfterBooting(service domainApp.IAppService) { +func SetAutoConnectAfterBooting(service domainApp.IAppUsecase) { time.Sleep(2 * time.Second) _ = service.Reconnect(context.Background()) } diff --git a/src/internal/rest/helpers/flushChatCsv.go b/src/ui/rest/helpers/flushChatCsv.go similarity index 100% rename from src/internal/rest/helpers/flushChatCsv.go rename to src/ui/rest/helpers/flushChatCsv.go diff --git a/src/internal/rest/message.go b/src/ui/rest/message.go similarity index 96% rename from src/internal/rest/message.go rename to src/ui/rest/message.go index 8d6d9fc..dfa1463 100644 --- a/src/internal/rest/message.go +++ b/src/ui/rest/message.go @@ -2,16 +2,16 @@ package rest import ( 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/pkg/whatsapp" "github.com/gofiber/fiber/v2" ) type Message struct { - Service domainMessage.IMessageService + Service domainMessage.IMessageUsecase } -func InitRestMessage(app *fiber.App, service domainMessage.IMessageService) Message { +func InitRestMessage(app *fiber.App, service domainMessage.IMessageUsecase) Message { rest := Message{Service: service} app.Post("/message/:message_id/reaction", rest.ReactMessage) app.Post("/message/:message_id/revoke", rest.RevokeMessage) diff --git a/src/internal/rest/middleware/basicauth.go b/src/ui/rest/middleware/basicauth.go similarity index 100% rename from src/internal/rest/middleware/basicauth.go rename to src/ui/rest/middleware/basicauth.go diff --git a/src/internal/rest/middleware/recovery.go b/src/ui/rest/middleware/recovery.go similarity index 100% rename from src/internal/rest/middleware/recovery.go rename to src/ui/rest/middleware/recovery.go diff --git a/src/internal/rest/newsletter.go b/src/ui/rest/newsletter.go similarity index 90% rename from src/internal/rest/newsletter.go rename to src/ui/rest/newsletter.go index 0386e9f..7b0d146 100644 --- a/src/internal/rest/newsletter.go +++ b/src/ui/rest/newsletter.go @@ -7,10 +7,10 @@ import ( ) type Newsletter struct { - Service domainNewsletter.INewsletterService + Service domainNewsletter.INewsletterUsecase } -func InitRestNewsletter(app *fiber.App, service domainNewsletter.INewsletterService) Newsletter { +func InitRestNewsletter(app *fiber.App, service domainNewsletter.INewsletterUsecase) Newsletter { rest := Newsletter{Service: service} app.Post("/newsletter/unfollow", rest.Unfollow) return rest diff --git a/src/internal/rest/send.go b/src/ui/rest/send.go similarity index 96% rename from src/internal/rest/send.go rename to src/ui/rest/send.go index e30e9e4..0e84e17 100644 --- a/src/internal/rest/send.go +++ b/src/ui/rest/send.go @@ -2,16 +2,16 @@ package rest import ( domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" + "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/pkg/whatsapp" "github.com/gofiber/fiber/v2" ) type Send struct { - Service domainSend.ISendService + Service domainSend.ISendUsecase } -func InitRestSend(app *fiber.App, service domainSend.ISendService) Send { +func InitRestSend(app *fiber.App, service domainSend.ISendUsecase) Send { rest := Send{Service: service} app.Post("/send/message", rest.SendText) app.Post("/send/image", rest.SendImage) diff --git a/src/internal/rest/user.go b/src/ui/rest/user.go similarity index 95% rename from src/internal/rest/user.go rename to src/ui/rest/user.go index 1dc244b..57ea47c 100644 --- a/src/internal/rest/user.go +++ b/src/ui/rest/user.go @@ -2,16 +2,16 @@ package rest import ( domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" + "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/pkg/whatsapp" "github.com/gofiber/fiber/v2" ) type User struct { - Service domainUser.IUserService + Service domainUser.IUserUsecase } -func InitRestUser(app *fiber.App, service domainUser.IUserService) User { +func InitRestUser(app *fiber.App, service domainUser.IUserUsecase) User { rest := User{Service: service} app.Get("/user/info", rest.UserInfo) app.Get("/user/avatar", rest.UserAvatar) diff --git a/src/internal/websocket/websocket.go b/src/ui/websocket/websocket.go similarity index 97% rename from src/internal/websocket/websocket.go rename to src/ui/websocket/websocket.go index 4c3fe3a..fab698e 100644 --- a/src/internal/websocket/websocket.go +++ b/src/ui/websocket/websocket.go @@ -76,7 +76,7 @@ func RunHub() { } } -func RegisterRoutes(app *fiber.App, service domainApp.IAppService) { +func RegisterRoutes(app *fiber.App, service domainApp.IAppUsecase) { app.Use("/ws", func(c *fiber.Ctx) error { if websocket.IsWebSocketUpgrade(c) { return c.Next() diff --git a/src/services/app.go b/src/usecase/app.go similarity index 99% rename from src/services/app.go rename to src/usecase/app.go index 51466dc..d16093d 100644 --- a/src/services/app.go +++ b/src/usecase/app.go @@ -1,4 +1,4 @@ -package services +package usecase import ( "context" @@ -26,7 +26,7 @@ type serviceApp struct { db *sqlstore.Container } -func NewAppService(waCli *whatsmeow.Client, db *sqlstore.Container) domainApp.IAppService { +func NewAppService(waCli *whatsmeow.Client, db *sqlstore.Container) domainApp.IAppUsecase { return &serviceApp{ WaCli: waCli, db: db, diff --git a/src/services/group.go b/src/usecase/group.go similarity index 89% rename from src/services/group.go rename to src/usecase/group.go index 2c31bb6..109aab2 100644 --- a/src/services/group.go +++ b/src/usecase/group.go @@ -1,4 +1,4 @@ -package services +package usecase import ( "context" @@ -6,24 +6,24 @@ import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/config" domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group" + "github.com/aldinokemal/go-whatsapp-web-multidevice/infrastructure/whatsapp" pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" - "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/types" ) -type groupService struct { +type serviceGroup struct { WaCli *whatsmeow.Client } -func NewGroupService(waCli *whatsmeow.Client) domainGroup.IGroupService { - return &groupService{ +func NewGroupService(waCli *whatsmeow.Client) domainGroup.IGroupUsecase { + return &serviceGroup{ WaCli: waCli, } } -func (service groupService) JoinGroupWithLink(ctx context.Context, request domainGroup.JoinGroupWithLinkRequest) (groupID string, err error) { +func (service serviceGroup) JoinGroupWithLink(ctx context.Context, request domainGroup.JoinGroupWithLinkRequest) (groupID string, err error) { if err = validations.ValidateJoinGroupWithLink(ctx, request); err != nil { return groupID, err } @@ -36,7 +36,7 @@ func (service groupService) JoinGroupWithLink(ctx context.Context, request domai return jid.String(), nil } -func (service groupService) LeaveGroup(ctx context.Context, request domainGroup.LeaveGroupRequest) (err error) { +func (service serviceGroup) LeaveGroup(ctx context.Context, request domainGroup.LeaveGroupRequest) (err error) { if err = validations.ValidateLeaveGroup(ctx, request); err != nil { return err } @@ -49,7 +49,7 @@ func (service groupService) LeaveGroup(ctx context.Context, request domainGroup. return service.WaCli.LeaveGroup(JID) } -func (service groupService) CreateGroup(ctx context.Context, request domainGroup.CreateGroupRequest) (groupID string, err error) { +func (service serviceGroup) CreateGroup(ctx context.Context, request domainGroup.CreateGroupRequest) (groupID string, err error) { if err = validations.ValidateCreateGroup(ctx, request); err != nil { return groupID, err } @@ -75,7 +75,7 @@ func (service groupService) CreateGroup(ctx context.Context, request domainGroup return groupInfo.JID.String(), nil } -func (service groupService) ManageParticipant(ctx context.Context, request domainGroup.ParticipantRequest) (result []domainGroup.ParticipantStatus, err error) { +func (service serviceGroup) ManageParticipant(ctx context.Context, request domainGroup.ParticipantRequest) (result []domainGroup.ParticipantStatus, err error) { if err = validations.ValidateParticipant(ctx, request); err != nil { return result, err } @@ -115,7 +115,7 @@ func (service groupService) ManageParticipant(ctx context.Context, request domai return result, nil } -func (service groupService) GetGroupRequestParticipants(ctx context.Context, request domainGroup.GetGroupRequestParticipantsRequest) (result []domainGroup.GetGroupRequestParticipantsResponse, err error) { +func (service serviceGroup) GetGroupRequestParticipants(ctx context.Context, request domainGroup.GetGroupRequestParticipantsRequest) (result []domainGroup.GetGroupRequestParticipantsResponse, err error) { if err = validations.ValidateGetGroupRequestParticipants(ctx, request); err != nil { return result, err } @@ -140,7 +140,7 @@ func (service groupService) GetGroupRequestParticipants(ctx context.Context, req return result, nil } -func (service groupService) ManageGroupRequestParticipants(ctx context.Context, request domainGroup.GroupRequestParticipantsRequest) (result []domainGroup.ParticipantStatus, err error) { +func (service serviceGroup) ManageGroupRequestParticipants(ctx context.Context, request domainGroup.GroupRequestParticipantsRequest) (result []domainGroup.ParticipantStatus, err error) { if err = validations.ValidateManageGroupRequestParticipants(ctx, request); err != nil { return result, err } @@ -179,7 +179,7 @@ func (service groupService) ManageGroupRequestParticipants(ctx context.Context, return result, nil } -func (service groupService) participantToJID(participants []string) ([]types.JID, error) { +func (service serviceGroup) participantToJID(participants []string) ([]types.JID, error) { var participantsJID []types.JID for _, participant := range participants { formattedParticipant := participant + config.WhatsappTypeUser diff --git a/src/services/message.go b/src/usecase/message.go similarity index 98% rename from src/services/message.go rename to src/usecase/message.go index 9433fac..debb819 100644 --- a/src/services/message.go +++ b/src/usecase/message.go @@ -1,4 +1,4 @@ -package services +package usecase import ( "context" @@ -6,7 +6,7 @@ import ( "time" 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/infrastructure/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "github.com/sirupsen/logrus" "go.mau.fi/whatsmeow" @@ -22,7 +22,7 @@ type serviceMessage struct { WaCli *whatsmeow.Client } -func NewMessageService(waCli *whatsmeow.Client) domainMessage.IMessageService { +func NewMessageService(waCli *whatsmeow.Client) domainMessage.IMessageUsecase { return &serviceMessage{ WaCli: waCli, } diff --git a/src/services/newsletter.go b/src/usecase/newsletter.go similarity index 72% rename from src/services/newsletter.go rename to src/usecase/newsletter.go index 0afcf42..476088d 100644 --- a/src/services/newsletter.go +++ b/src/usecase/newsletter.go @@ -1,24 +1,25 @@ -package services +package usecase import ( "context" + domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newsletter" - "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" + "github.com/aldinokemal/go-whatsapp-web-multidevice/infrastructure/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "go.mau.fi/whatsmeow" ) -type newsletterService struct { +type serviceNewsletter struct { WaCli *whatsmeow.Client } -func NewNewsletterService(waCli *whatsmeow.Client) domainNewsletter.INewsletterService { - return &newsletterService{ +func NewNewsletterService(waCli *whatsmeow.Client) domainNewsletter.INewsletterUsecase { + return &serviceNewsletter{ WaCli: waCli, } } -func (service newsletterService) Unfollow(ctx context.Context, request domainNewsletter.UnfollowRequest) (err error) { +func (service serviceNewsletter) Unfollow(ctx context.Context, request domainNewsletter.UnfollowRequest) (err error) { if err = validations.ValidateUnfollowNewsletter(ctx, request); err != nil { return err } diff --git a/src/services/send.go b/src/usecase/send.go similarity index 99% rename from src/services/send.go rename to src/usecase/send.go index 48a0383..35eaa23 100644 --- a/src/services/send.go +++ b/src/usecase/send.go @@ -1,4 +1,4 @@ -package services +package usecase import ( "context" @@ -10,10 +10,10 @@ import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/config" "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" - "github.com/aldinokemal/go-whatsapp-web-multidevice/internal/rest/helpers" + "github.com/aldinokemal/go-whatsapp-web-multidevice/infrastructure/whatsapp" pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" - "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" + "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/rest/helpers" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "github.com/disintegration/imaging" fiberUtils "github.com/gofiber/fiber/v2/utils" @@ -27,10 +27,10 @@ import ( type serviceSend struct { WaCli *whatsmeow.Client - appService app.IAppService + appService app.IAppUsecase } -func NewSendService(waCli *whatsmeow.Client, appService app.IAppService) domainSend.ISendService { +func NewSendService(waCli *whatsmeow.Client, appService app.IAppUsecase) domainSend.ISendUsecase { return &serviceSend{ WaCli: waCli, appService: appService, diff --git a/src/services/user.go b/src/usecase/user.go similarity index 88% rename from src/services/user.go rename to src/usecase/user.go index 91a6bad..89fbb04 100644 --- a/src/services/user.go +++ b/src/usecase/user.go @@ -1,4 +1,4 @@ -package services +package usecase import ( "bytes" @@ -9,8 +9,8 @@ import ( "time" domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" + "github.com/aldinokemal/go-whatsapp-web-multidevice/infrastructure/whatsapp" pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" - "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "github.com/disintegration/imaging" "go.mau.fi/whatsmeow" @@ -18,17 +18,17 @@ import ( "go.mau.fi/whatsmeow/types" ) -type userService struct { +type serviceUser struct { WaCli *whatsmeow.Client } -func NewUserService(waCli *whatsmeow.Client) domainUser.IUserService { - return &userService{ +func NewUserService(waCli *whatsmeow.Client) domainUser.IUserUsecase { + return &serviceUser{ WaCli: waCli, } } -func (service userService) Info(ctx context.Context, request domainUser.InfoRequest) (response domainUser.InfoResponse, err error) { +func (service serviceUser) Info(ctx context.Context, request domainUser.InfoRequest) (response domainUser.InfoResponse, err error) { err = validations.ValidateUserInfo(ctx, request) if err != nil { return response, err @@ -71,7 +71,7 @@ func (service userService) Info(ctx context.Context, request domainUser.InfoRequ return response, nil } -func (service userService) Avatar(ctx context.Context, request domainUser.AvatarRequest) (response domainUser.AvatarResponse, err error) { +func (service serviceUser) Avatar(ctx context.Context, request domainUser.AvatarRequest) (response domainUser.AvatarResponse, err error) { chanResp := make(chan domainUser.AvatarResponse) chanErr := make(chan error) @@ -118,7 +118,7 @@ func (service userService) Avatar(ctx context.Context, request domainUser.Avatar } -func (service userService) MyListGroups(_ context.Context) (response domainUser.MyListGroupsResponse, err error) { +func (service serviceUser) MyListGroups(_ context.Context) (response domainUser.MyListGroupsResponse, err error) { whatsapp.MustLogin(service.WaCli) groups, err := service.WaCli.GetJoinedGroups() @@ -132,7 +132,7 @@ func (service userService) MyListGroups(_ context.Context) (response domainUser. return response, nil } -func (service userService) MyListNewsletter(_ context.Context) (response domainUser.MyListNewsletterResponse, err error) { +func (service serviceUser) MyListNewsletter(_ context.Context) (response domainUser.MyListNewsletterResponse, err error) { whatsapp.MustLogin(service.WaCli) datas, err := service.WaCli.GetSubscribedNewsletters() @@ -146,7 +146,7 @@ func (service userService) MyListNewsletter(_ context.Context) (response domainU return response, nil } -func (service userService) MyPrivacySetting(ctx context.Context) (response domainUser.MyPrivacySettingResponse, err error) { +func (service serviceUser) MyPrivacySetting(ctx context.Context) (response domainUser.MyPrivacySettingResponse, err error) { whatsapp.MustLogin(service.WaCli) resp, err := service.WaCli.TryFetchPrivacySettings(ctx, true) @@ -161,7 +161,7 @@ func (service userService) MyPrivacySetting(ctx context.Context) (response domai return response, nil } -func (service userService) MyListContacts(ctx context.Context) (response domainUser.MyListContactsResponse, err error) { +func (service serviceUser) MyListContacts(ctx context.Context) (response domainUser.MyListContactsResponse, err error) { whatsapp.MustLogin(service.WaCli) contacts, err := service.WaCli.Store.Contacts.GetAllContacts(ctx) @@ -179,7 +179,7 @@ func (service userService) MyListContacts(ctx context.Context) (response domainU return response, nil } -func (service userService) ChangeAvatar(ctx context.Context, request domainUser.ChangeAvatarRequest) (err error) { +func (service serviceUser) ChangeAvatar(ctx context.Context, request domainUser.ChangeAvatarRequest) (err error) { whatsapp.MustLogin(service.WaCli) file, err := request.Avatar.Open() @@ -233,7 +233,7 @@ func (service userService) ChangeAvatar(ctx context.Context, request domainUser. return nil } -func (service userService) ChangePushName(ctx context.Context, request domainUser.ChangePushNameRequest) (err error) { +func (service serviceUser) ChangePushName(ctx context.Context, request domainUser.ChangePushNameRequest) (err error) { whatsapp.MustLogin(service.WaCli) err = service.WaCli.SendAppState(ctx, appstate.BuildSettingPushName(request.PushName)) From a34621cba71b36f2262c22f5e3128fb0d6f6b7a9 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sun, 25 May 2025 20:56:07 +0700 Subject: [PATCH 3/7] docs: clarify production mode in docker examples refactor: centralize folder creation in root initialization chore: bump version to v6.0.1 and update dependencies The commit updates documentation to explicitly mention REST mode in Docker examples, removes duplicate folder creation logic from individual command files by consolidating it in root initialization, increments the app version, and cleans up unused dependencies while maintaining proper error handling. --- readme.md | 4 ++-- src/cmd/mcp.go | 7 ------- src/cmd/rest.go | 9 +-------- src/cmd/root.go | 6 ++++++ src/config/settings.go | 2 +- src/go.mod | 11 ----------- src/go.sum | 24 ------------------------ 7 files changed, 10 insertions(+), 53 deletions(-) diff --git a/readme.md b/readme.md index e8cfbfd..c46a65a 100644 --- a/readme.md +++ b/readme.md @@ -156,13 +156,13 @@ make sure you have running MCP server, `./whatsapp mcp` } ``` -### Production Mode (docker) +### Production Mode REST (docker) ```bash docker run --detach --publish=3000:3000 --name=whatsapp --restart=always --volume=$(docker volume create --name=whatsapp):/app/storages aldinokemal2104/go-whatsapp-web-multidevice rest --autoreply="Dont't reply this message please" ``` -### Production Mode (docker compose) +### Production Mode REST (docker compose) create `docker-compose.yml` file with the following configuration: diff --git a/src/cmd/mcp.go b/src/cmd/mcp.go index 8bfd1af..7f0d846 100644 --- a/src/cmd/mcp.go +++ b/src/cmd/mcp.go @@ -5,7 +5,6 @@ import ( "log" "github.com/aldinokemal/go-whatsapp-web-multidevice/config" - "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/mcp" "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/rest/helpers" "github.com/mark3labs/mcp-go/server" @@ -27,12 +26,6 @@ func init() { } func mcpServer(_ *cobra.Command, _ []string) { - // Prepare folders if not exist - err := utils.CreateFolder(config.PathQrCode, config.PathSendItems, config.PathStorages, config.PathMedia) - if err != nil { - log.Fatalf("Failed to create folders: %v", err) - } - // Set auto reconnect to whatsapp server after booting go helpers.SetAutoConnectAfterBooting(appUsecase) // Set auto reconnect checking diff --git a/src/cmd/rest.go b/src/cmd/rest.go index b2c2dfc..9189d30 100644 --- a/src/cmd/rest.go +++ b/src/cmd/rest.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/aldinokemal/go-whatsapp-web-multidevice/config" - "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/rest" "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/rest/helpers" "github.com/aldinokemal/go-whatsapp-web-multidevice/ui/rest/middleware" @@ -34,12 +33,6 @@ func init() { rootCmd.AddCommand(restCmd) } func restServer(_ *cobra.Command, _ []string) { - //preparing folder if not exist - err := utils.CreateFolder(config.PathQrCode, config.PathSendItems, config.PathStorages, config.PathMedia) - if err != nil { - log.Fatalln(err) - } - engine := html.NewFileSystem(http.FS(EmbedIndex), ".html") engine.AddFunc("isEnableBasicAuth", func(token any) bool { return token != nil @@ -116,7 +109,7 @@ func restServer(_ *cobra.Command, _ []string) { go helpers.StartAutoFlushChatStorage() } - if err = app.Listen(":" + config.AppPort); err != nil { + if err := app.Listen(":" + config.AppPort); err != nil { log.Fatalln("Failed to start: ", err.Error()) } } diff --git a/src/cmd/root.go b/src/cmd/root.go index 389ad20..33013c1 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -17,6 +17,7 @@ import ( "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/usecase" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" "go.mau.fi/whatsmeow" @@ -188,6 +189,11 @@ func initApp() { if config.AppDebug { config.WhatsappLogLevel = "DEBUG" } + //preparing folder if not exist + err := utils.CreateFolder(config.PathQrCode, config.PathSendItems, config.PathStorages, config.PathMedia) + if err != nil { + logrus.Errorln(err) + } ctx := context.Background() whatsappDB = whatsapp.InitWaDB(ctx) diff --git a/src/config/settings.go b/src/config/settings.go index d4e9979..62d0ae0 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -5,7 +5,7 @@ import ( ) var ( - AppVersion = "v6.0.0" + AppVersion = "v6.0.1" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" diff --git a/src/go.mod b/src/go.mod index 7e25a4d..d74b214 100644 --- a/src/go.mod +++ b/src/go.mod @@ -31,8 +31,6 @@ require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/buger/jsonparser v1.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fasthttp/websocket v1.5.12 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect @@ -41,16 +39,12 @@ require ( github.com/gofiber/utils v1.1.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/invopop/jsonschema v0.12.0 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/metoro-io/mcp-golang v0.13.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/zerolog v1.34.0 // indirect @@ -61,12 +55,7 @@ require ( github.com/spf13/cast v1.8.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tidwall/gjson v1.18.0 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.1 // indirect - github.com/tidwall/sjson v1.2.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.mau.fi/util v0.8.7 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/src/go.sum b/src/go.sum index a57439f..403d22f 100644 --- a/src/go.sum +++ b/src/go.sum @@ -11,10 +11,6 @@ github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmg github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= -github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -54,9 +50,6 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= -github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -65,8 +58,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mark3labs/mcp-go v0.29.0 h1:sH1NBcumKskhxqYzhXfGc201D7P76TVXiT0fGVhabeI= github.com/mark3labs/mcp-go v0.29.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -80,13 +71,10 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/metoro-io/mcp-golang v0.13.0 h1:54TFBJIW76VRB55CJovQQje9x4GnXg0BQQwGRtXrbCE= -github.com/metoro-io/mcp-golang v0.13.0/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb h1:3PrKuO92dUTMrQ9dx0YNejC6U/Si6jqKmyQ9vWjwqR4= github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -127,22 +115,10 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.62.0 h1:8dKRBX/y2rCzyc6903Zu1+3qN0H/d2MsxPPmVNamiH0= github.com/valyala/fasthttp v1.62.0/go.mod h1:FCINgr4GKdKqV8Q0xv8b+UxPV+H/O5nNFo3D+r54Htg= -github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= -github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= From 20d027c12ae24db21c010df7f028050f02c0a5ac Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sun, 25 May 2025 21:27:09 +0700 Subject: [PATCH 4/7] feat: support backward compability This commit introduces a .dockerignore file to exclude unnecessary files from the Docker build context. Additionally, the Dockerfile is updated to set the default command to "rest" when the container starts, ensuring proper execution of the application. --- .dockerignore | 10 ++++++++++ docker/golang.Dockerfile | 4 +++- readme.md | 5 +---- 3 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..66a439b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.idea +.vscode +.git +.gitignore +.env +.env.local +.env.development +.env.test +.env.production +docker diff --git a/docker/golang.Dockerfile b/docker/golang.Dockerfile index a8bd640..7fea96d 100644 --- a/docker/golang.Dockerfile +++ b/docker/golang.Dockerfile @@ -20,4 +20,6 @@ WORKDIR /app # Copy compiled from builder. COPY --from=builder /app/whatsapp /app/whatsapp # Run the binary. -ENTRYPOINT ["/app/whatsapp"] \ No newline at end of file +ENTRYPOINT ["/app/whatsapp"] + +CMD [ "rest" ] \ No newline at end of file diff --git a/readme.md b/readme.md index c46a65a..cf289aa 100644 --- a/readme.md +++ b/readme.md @@ -159,7 +159,7 @@ make sure you have running MCP server, `./whatsapp mcp` ### Production Mode REST (docker) ```bash -docker run --detach --publish=3000:3000 --name=whatsapp --restart=always --volume=$(docker volume create --name=whatsapp):/app/storages aldinokemal2104/go-whatsapp-web-multidevice rest --autoreply="Dont't reply this message please" +docker run --detach --publish=3000:3000 --name=whatsapp --restart=always --volume=$(docker volume create --name=whatsapp):/app/storages aldinokemal2104/go-whatsapp-web-multidevice --autoreply="Dont't reply this message please" ``` ### Production Mode REST (docker compose) @@ -177,7 +177,6 @@ services: volumes: - whatsapp:/app/storages command: - - rest - --basic-auth=admin:admin - --port=3000 - --debug=true @@ -200,8 +199,6 @@ services: - "3000:3000" volumes: - whatsapp:/app/storages - command: - - rest environment: - APP_BASIC_AUTH=admin:admin - APP_PORT=3000 From 13c42c1184fda1f462a4fd2732228a546fada464 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Mon, 26 May 2025 06:06:27 +0700 Subject: [PATCH 5/7] chore: docs update --- readme.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/readme.md b/readme.md index cf289aa..35ca6df 100644 --- a/readme.md +++ b/readme.md @@ -20,6 +20,14 @@ Download: - [Release](https://github.com/aldinokemal/go-whatsapp-web-multidevice/releases/latest) - [Docker Image](https://hub.docker.com/r/aldinokemal2104/go-whatsapp-web-multidevice/tags) +## Breaking Changes + +- `v6` + - For REST mode, you need to run ` rest` instead of `` + - for example: `./whatsapp rest` instead of ~~./whatsapp~~ + - For MCP mode, you need to run ` mcp` + - for example: `./whatsapp mcp` + ## Feature - Send WhatsApp message via http API, [docs/openapi.yml](./docs/openapi.yaml) for more details @@ -177,6 +185,7 @@ services: volumes: - whatsapp:/app/storages command: + - rest - --basic-auth=admin:admin - --port=3000 - --debug=true From f0cfcbb268ed75a474baddf96d1374f382c242f8 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sun, 8 Jun 2025 06:12:12 +0700 Subject: [PATCH 6/7] chore: update dependencies and fix configuration - update to v6.0.2 - enable foreign keys in SQLite database - fix docker documentation example - update dependencies including whatsmeow and mcp-go - improve database URI flag documentation --- docs/openapi.yaml | 166 +++++++++++++++++++++++++++++++++++++---- readme.md | 65 ++++++++++++---- src/.env.example | 2 +- src/cmd/root.go | 2 +- src/config/settings.go | 2 +- src/go.mod | 16 ++-- src/go.sum | 32 ++++---- 7 files changed, 228 insertions(+), 57 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 3b34482..f95c6a3 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.0" info: title: WhatsApp API MultiDevice - version: 5.4.0 + version: 6.0.0 description: This API is used for sending whatsapp via API servers: - url: http://localhost:3000 @@ -175,6 +175,12 @@ paths: type: boolean example: true description: Whether to fetch a preview of the avatar + - name: is_community + in: query + schema: + type: boolean + example: false + description: Whether to fetch a community avatar responses: '200': description: OK @@ -367,6 +373,10 @@ paths: type: string example: 3EB089B9D6ADD58153C561 description: Message ID that you want reply + is_forwarded: + type: boolean + example: false + description: Whether this is a forwarded message responses: '200': description: OK @@ -422,6 +432,10 @@ paths: type: boolean example: false description: Compress image + is_forwarded: + type: boolean + example: false + description: Whether this is a forwarded message responses: '200': description: OK @@ -461,6 +475,10 @@ paths: type: string format: binary description: Audio to send + is_forwarded: + type: boolean + example: false + description: Whether this is a forwarded message responses: '200': description: OK @@ -504,6 +522,10 @@ paths: type: string format: binary description: File to send + is_forwarded: + type: boolean + example: false + description: Whether this is a forwarded message responses: '200': description: OK @@ -545,7 +567,7 @@ paths: description: Caption to send view_once: type: boolean - example: 'false' + example: false description: View once video: type: string @@ -553,8 +575,12 @@ paths: description: Video to send compress: type: boolean - example: 'false' + example: false description: Compress video + is_forwarded: + type: boolean + example: false + description: Whether this is a forwarded message responses: '200': description: OK @@ -598,6 +624,10 @@ paths: type: string example: '6289685024992' description: Contact phone number + is_forwarded: + type: boolean + example: false + description: Whether this is a forwarded message responses: '200': description: OK @@ -641,6 +671,10 @@ paths: type: string example: 'Halo ini contoh caption' description: Caption to send + is_forwarded: + type: boolean + example: false + description: Whether this is a forwarded message responses: '200': description: OK @@ -684,6 +718,10 @@ paths: type: string example: '110.370529' description: Longitude coordinate + is_forwarded: + type: boolean + example: false + description: Whether this is a forwarded message responses: '200': description: OK @@ -771,13 +809,17 @@ paths: schema: type: object properties: - presence: + type: type: string - description: The presence status to send + description: The presence type to send enum: [available, unavailable] example: 'available' + is_forwarded: + type: boolean + example: false + description: Whether this is a forwarded message required: - - presence + - type responses: '200': description: OK @@ -1020,6 +1062,94 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorInternalServer' + /message/{message_id}/star: + post: + operationId: starMessage + tags: + - message + summary: Star message + parameters: + - in: path + name: message_id + schema: + type: string + required: true + description: Message ID + requestBody: + content: + application/json: + schema: + type: object + properties: + phone: + type: string + example: '62819273192397132@s.whatsapp.net' + description: Phone number with country code + required: + - phone + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GenericResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorBadRequest' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorInternalServer' + /message/{message_id}/unstar: + post: + operationId: unstarMessage + tags: + - message + summary: Unstar message + parameters: + - in: path + name: message_id + schema: + type: string + required: true + description: Message ID + requestBody: + content: + application/json: + schema: + type: object + properties: + phone: + type: string + example: '62819273192397132@s.whatsapp.net' + description: Phone number with country code + required: + - phone + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GenericResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorBadRequest' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorInternalServer' /group: post: operationId: createGroup @@ -1269,13 +1399,15 @@ paths: type: string example: '120363024512399999@g.us' description: The group ID - participant_id: - type: string - example: '6281234567890' - description: The participant's WhatsApp ID to approve + participants: + type: array + items: + type: string + example: ['6281234567890'] + description: Array of participant WhatsApp IDs to approve required: - group_id - - participant_id + - participants responses: '200': description: OK @@ -1311,13 +1443,15 @@ paths: type: string example: '120363024512399999@g.us' description: The group ID - participant_id: - type: string - example: '6281234567890' - description: The participant's WhatsApp ID to reject + participants: + type: array + items: + type: string + example: ['6281234567890'] + description: Array of participant WhatsApp IDs to reject required: - group_id - - participant_id + - participants responses: '200': description: OK diff --git a/readme.md b/readme.md index 35ca6df..2136097 100644 --- a/readme.md +++ b/readme.md @@ -68,19 +68,52 @@ can be set in three ways (in order of priority): ### Environment Variables +You can configure the application using environment variables. Configuration can be set in three ways (in order of priority): + +1. Command-line flags (highest priority) +2. Environment variables +3. `.env` file (lowest priority) + To use environment variables: -1. Copy `.env.example` to `.env` in your project root +1. Copy `.env.example` to `.env` in your project root (`cp src/.env.example src/.env`) 2. Modify the values in `.env` according to your needs 3. Or set the same variables as system environment variables -See [.env.example](./src/.env.example) for all available configuration options. +#### Available Environment Variables + +| Variable | Description | Default | Example | +|----------|-------------|---------|---------| +| `APP_PORT` | Application port | `3000` | `APP_PORT=8080` | +| `APP_DEBUG` | Enable debug logging | `false` | `APP_DEBUG=true` | +| `APP_OS` | OS name (device name in WhatsApp) | `Chrome` | `APP_OS=MyApp` | +| `APP_BASIC_AUTH` | Basic authentication credentials | - | `APP_BASIC_AUTH=user1:pass1,user2:pass2` | +| `APP_CHAT_FLUSH_INTERVAL` | Chat flush interval in days | `7` | `APP_CHAT_FLUSH_INTERVAL=30` | +| `DB_URI` | Database connection URI | `file:storages/whatsapp.db?_foreign_keys=on` | `DB_URI=postgres://user:pass@host/db` | +| `WHATSAPP_AUTO_REPLY` | Auto-reply message | - | `WHATSAPP_AUTO_REPLY="Auto reply message"` | +| `WHATSAPP_WEBHOOK` | Webhook URL(s) for events (comma-separated) | - | `WHATSAPP_WEBHOOK=https://webhook.site/xxx` | +| `WHATSAPP_WEBHOOK_SECRET` | Webhook secret for validation | `secret` | `WHATSAPP_WEBHOOK_SECRET=super-secret-key` | +| `WHATSAPP_ACCOUNT_VALIDATION` | Enable account validation | `true` | `WHATSAPP_ACCOUNT_VALIDATION=false` | +| `WHATSAPP_CHAT_STORAGE` | Enable chat storage | `true` | `WHATSAPP_CHAT_STORAGE=false` | Note: Command-line flags will override any values set in environment variables or `.env` file. -- For more command `./main --help` +- For more command `./whatsapp --help` + +## Requirements -## Required (without docker) +### System Requirements + +- **Go 1.24.0 or higher** (for building from source) +- **FFmpeg** (for media processing) + +### Platform Support + +- Linux (x86_64, ARM64) +- macOS (Intel, Apple Silicon) +- Windows (x86_64) - WSL recommended + +### Dependencies (without docker) - Mac OS: - `brew install ffmpeg` @@ -89,7 +122,7 @@ Note: Command-line flags will override any values set in environment variables o - `sudo apt update` - `sudo apt install ffmpeg` - Windows (not recomended, prefer using [WSL](https://docs.microsoft.com/en-us/windows/wsl/install)): - - install ffmpeg, download [here](https://www.ffmpeg.org/download.html#build-windows) + - install ffmpeg, [download here](https://www.ffmpeg.org/download.html#build-windows) - add to ffmpeg to [environment variable](https://www.google.com/search?q=windows+add+to+environment+path) ## How to use @@ -99,7 +132,7 @@ Note: Command-line flags will override any values set in environment variables o 1. Clone this repo: `git clone https://github.com/aldinokemal/go-whatsapp-web-multidevice` 2. Open the folder that was cloned via cmd/terminal. 3. run `cd src` -4. run `go run main.go` +4. run `go run . rest` (for REST API mode) 5. Open `http://localhost:3000` ### Docker (you don't need to install in required) @@ -118,9 +151,9 @@ Note: Command-line flags will override any values set in environment variables o 1. Linux & MacOS: `go build -o whatsapp` 2. Windows (CMD / PowerShell): `go build -o whatsapp.exe` 5. run - 1. Linux & MacOS: `./whatsapp` + 1. Linux & MacOS: `./whatsapp rest` (for REST API mode) 1. run `./whatsapp --help` for more detail flags - 2. Windows: `.\whatsapp.exe` or you can double-click it + 2. Windows: `.\whatsapp.exe rest` (for REST API mode) 1. run `.\whatsapp.exe --help` for more detail flags 6. open `http://localhost:3000` in browser @@ -131,7 +164,7 @@ This application can also run as an MCP server, allowing AI agents and tools to 1. Clone this repo `git clone https://github.com/aldinokemal/go-whatsapp-web-multidevice` 2. Open the folder that was cloned via cmd/terminal. 3. run `cd src` -4. run `go run main.go mcp` or build the binary and run `./whatsapp mcp` +4. run `go run . mcp` or build the binary and run `./whatsapp mcp` 5. The MCP server will start on `http://localhost:8080` by default #### MCP Server Options @@ -153,7 +186,9 @@ This application can also run as an MCP server, allowing AI agents and tools to ### MCP Configuration -make sure you have running MCP server, `./whatsapp mcp` +Make sure you have the MCP server running: `./whatsapp mcp` + +For AI tools that support MCP with SSE (like Cursor), add this configuration: ```json { @@ -162,12 +197,13 @@ make sure you have running MCP server, `./whatsapp mcp` "url": "http://localhost:8080/sse" } } +} ``` ### Production Mode REST (docker) ```bash -docker run --detach --publish=3000:3000 --name=whatsapp --restart=always --volume=$(docker volume create --name=whatsapp):/app/storages aldinokemal2104/go-whatsapp-web-multidevice --autoreply="Dont't reply this message please" +docker run --detach --publish=3000:3000 --name=whatsapp --restart=always --volume=$(docker volume create --name=whatsapp):/app/storages aldinokemal2104/go-whatsapp-web-multidevice rest --autoreply="Dont't reply this message please" ``` ### Production Mode REST (docker compose) @@ -272,6 +308,7 @@ You can fork or edit this source code ! | ✅ | Edit Message | POST | /message/:message_id/update | | ✅ | Read Message (DM) | POST | /message/:message_id/read | | ✅ | Star Message | POST | /message/:message_id/star | +| ✅ | Unstar Message | POST | /message/:message_id/unstar | | ✅ | Join Group With Link | POST | /group/join-with-link | | ✅ | Leave Group | POST | /group/leave | | ✅ | Create Group | POST | /group | @@ -279,9 +316,9 @@ You can fork or edit this source code ! | ✅ | Remove Participant in Group | POST | /group/participants/remove | | ✅ | Promote Participant in Group | POST | /group/participants/promote | | ✅ | Demote Participant in Group | POST | /group/participants/demote | -| ✅ | List Requested Participants in Group | POST | /group/participants/requested | -| ✅ | Approve Requested Participant in Group | POST | /group/participants/requested/approve | -| ✅ | Reject Requested Participant in Group | POST | /group/participants/requested/reject | +| ✅ | List Requested Participants in Group | GET | /group/participant-requests | +| ✅ | Approve Requested Participant in Group | POST | /group/participant-requests/approve | +| ✅ | Reject Requested Participant in Group | POST | /group/participant-requests/reject | | ✅ | Unfollow Newsletter | POST | /newsletter/unfollow | ```txt diff --git a/src/.env.example b/src/.env.example index 4c5e126..80a226a 100644 --- a/src/.env.example +++ b/src/.env.example @@ -6,7 +6,7 @@ APP_BASIC_AUTH=user1:pass1,user2:pass2 APP_CHAT_FLUSH_INTERVAL=7 # Database Settings -DB_URI="file:storages/whatsapp.db?_foreign_keys=off" +DB_URI="file:storages/whatsapp.db?_foreign_keys=on" # WhatsApp Settings WHATSAPP_AUTO_REPLY="Auto reply message" diff --git a/src/cmd/root.go b/src/cmd/root.go index 33013c1..1f76819 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -149,7 +149,7 @@ func initFlags() { &config.DBURI, "db-uri", "", config.DBURI, - `the database uri to store the connection data database uri (by default, we'll use sqlite3 under storages/whatsapp.db). database uri --db-uri | example: --db-uri="file:storages/whatsapp.db?_foreign_keys=off or postgres://user:password@localhost:5432/whatsapp"`, + `the database uri to store the connection data database uri (by default, we'll use sqlite3 under storages/whatsapp.db). database uri --db-uri | example: --db-uri="file:storages/whatsapp.db?_foreign_keys=on or postgres://user:password@localhost:5432/whatsapp"`, ) // WhatsApp flags diff --git a/src/config/settings.go b/src/config/settings.go index 62d0ae0..053f550 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -5,7 +5,7 @@ import ( ) var ( - AppVersion = "v6.0.1" + AppVersion = "v6.0.2" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" diff --git a/src/go.mod b/src/go.mod index d74b214..e2aded4 100644 --- a/src/go.mod +++ b/src/go.mod @@ -12,7 +12,7 @@ require ( github.com/gofiber/websocket/v2 v2.2.1 github.com/google/uuid v1.6.0 github.com/lib/pq v1.10.9 - github.com/mark3labs/mcp-go v0.29.0 + github.com/mark3labs/mcp-go v0.31.0 github.com/mattn/go-sqlite3 v1.14.28 github.com/sirupsen/logrus v1.9.3 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e @@ -21,8 +21,8 @@ require ( github.com/stretchr/testify v1.10.0 github.com/valyala/fasthttp v1.62.0 go.mau.fi/libsignal v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250521125706-91ac75c2f61a - golang.org/x/image v0.27.0 + go.mau.fi/whatsmeow v0.0.0-20250606170101-3afe34f8ab8f + golang.org/x/image v0.28.0 google.golang.org/protobuf v1.36.6 ) @@ -52,17 +52,17 @@ require ( github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.14.0 // indirect - github.com/spf13/cast v1.8.0 // indirect + github.com/spf13/cast v1.9.2 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.mau.fi/util v0.8.7 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect - golang.org/x/net v0.40.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect + golang.org/x/net v0.41.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect + golang.org/x/text v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/src/go.sum b/src/go.sum index 403d22f..9a954eb 100644 --- a/src/go.sum +++ b/src/go.sum @@ -58,8 +58,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mark3labs/mcp-go v0.29.0 h1:sH1NBcumKskhxqYzhXfGc201D7P76TVXiT0fGVhabeI= -github.com/mark3labs/mcp-go v0.29.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= +github.com/mark3labs/mcp-go v0.31.0 h1:4UxSV8aM770OPmTvaVe/b1rA2oZAjBMhGBfUgOGut+4= +github.com/mark3labs/mcp-go v0.31.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -100,8 +100,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= -github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -128,8 +128,8 @@ go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= -go.mau.fi/whatsmeow v0.0.0-20250521125706-91ac75c2f61a h1:PYtzz5wdma64I47CiquGicyubzg3HIPkH/jMzpmHu8g= -go.mau.fi/whatsmeow v0.0.0-20250521125706-91ac75c2f61a/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250606170101-3afe34f8ab8f h1:8csRM0kOS9nGgT162JFwi3FZ93NPM6fWVf/d5AfTceA= +go.mau.fi/whatsmeow v0.0.0-20250606170101-3afe34f8ab8f/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -138,13 +138,13 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4= +golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= -golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE= +golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -159,8 +159,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -202,8 +202,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 93bf10b4ad74d8a992c6a181139f1ef9320309d1 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Tue, 10 Jun 2025 11:58:45 +0700 Subject: [PATCH 7/7] feat: add IsOnWhatsApp endpoint to check user availability - Add /user/check endpoint with phone query parameter - Implement CheckRequest and CheckResponse domain types - Add IsOnWhatsApp method to user usecase interface and implementation - Create AccountUserCheck Vue component for UI interaction - Update OpenAPI documentation with new endpoint schema - Bump version to 6.1.0 for new feature release --- docs/openapi.yaml | 51 ++++++++++++- src/domains/user/account.go | 8 ++ src/domains/user/user.go | 1 + src/ui/rest/user.go | 17 +++++ src/usecase/user.go | 10 +++ src/views/components/AccountUserCheck.js | 95 ++++++++++++++++++++++++ src/views/index.html | 4 +- 7 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/views/components/AccountUserCheck.js diff --git a/docs/openapi.yaml b/docs/openapi.yaml index f95c6a3..53d8317 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.0" info: title: WhatsApp API MultiDevice - version: 6.0.0 + version: 6.1.0 description: This API is used for sending whatsapp via API servers: - url: http://localhost:3000 @@ -348,6 +348,38 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorInternalServer' + /user/check: + get: + operationId: userCheck + tags: + - user + summary: Check if user is on WhatsApp + parameters: + - name: phone + in: query + schema: + type: string + example: '628912344551' + description: Phone number with country code + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UserCheckResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorBadRequest' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorInternalServer' /send/message: post: @@ -2137,4 +2169,19 @@ components: requested_at: type: string format: date-time - example: "2024-10-11T21:27:29+07:00" \ No newline at end of file + example: "2024-10-11T21:27:29+07:00" + UserCheckResponse: + type: object + properties: + code: + type: string + example: SUCCESS + message: + type: string + example: Success check user + results: + type: object + properties: + is_on_whatsapp: + type: boolean + example: true \ No newline at end of file diff --git a/src/domains/user/account.go b/src/domains/user/account.go index c0fb6b5..a60090e 100644 --- a/src/domains/user/account.go +++ b/src/domains/user/account.go @@ -73,3 +73,11 @@ type MyListContactsResponseData struct { type ChangePushNameRequest struct { PushName string `json:"push_name" form:"push_name"` } + +type CheckRequest struct { + Phone string `json:"phone" query:"phone"` +} + +type CheckResponse struct { + IsOnWhatsApp bool `json:"is_on_whatsapp"` +} diff --git a/src/domains/user/user.go b/src/domains/user/user.go index 902fb52..fc984eb 100644 --- a/src/domains/user/user.go +++ b/src/domains/user/user.go @@ -13,4 +13,5 @@ type IUserUsecase interface { MyListNewsletter(ctx context.Context) (response MyListNewsletterResponse, err error) MyPrivacySetting(ctx context.Context) (response MyPrivacySettingResponse, err error) MyListContacts(ctx context.Context) (response MyListContactsResponse, err error) + IsOnWhatsApp(ctx context.Context, request CheckRequest) (response CheckResponse, err error) } diff --git a/src/ui/rest/user.go b/src/ui/rest/user.go index 57ea47c..4281f17 100644 --- a/src/ui/rest/user.go +++ b/src/ui/rest/user.go @@ -21,6 +21,7 @@ func InitRestUser(app *fiber.App, service domainUser.IUserUsecase) User { app.Get("/user/my/groups", rest.UserMyListGroups) app.Get("/user/my/newsletters", rest.UserMyListNewsletter) app.Get("/user/my/contacts", rest.UserMyListContacts) + app.Get("/user/check", rest.UserCheck) return rest } @@ -141,3 +142,19 @@ func (controller *User) UserChangePushName(c *fiber.Ctx) error { Message: "Success change push name", }) } + +func (controller *User) UserCheck(c *fiber.Ctx) error { + var request domainUser.CheckRequest + err := c.QueryParser(&request) + utils.PanicIfNeeded(err) + + response, err := controller.Service.IsOnWhatsApp(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Success check user", + Results: response, + }) +} diff --git a/src/usecase/user.go b/src/usecase/user.go index 89fbb04..13e890b 100644 --- a/src/usecase/user.go +++ b/src/usecase/user.go @@ -242,3 +242,13 @@ func (service serviceUser) ChangePushName(ctx context.Context, request domainUse } return nil } + +func (service serviceUser) IsOnWhatsApp(ctx context.Context, request domainUser.CheckRequest) (response domainUser.CheckResponse, err error) { + whatsapp.MustLogin(service.WaCli) + + whatsapp.SanitizePhone(&request.Phone) + + response.IsOnWhatsApp = whatsapp.IsOnWhatsapp(service.WaCli, request.Phone) + + return response, nil +} diff --git a/src/views/components/AccountUserCheck.js b/src/views/components/AccountUserCheck.js new file mode 100644 index 0000000..c417c68 --- /dev/null +++ b/src/views/components/AccountUserCheck.js @@ -0,0 +1,95 @@ +import FormRecipient from "./generic/FormRecipient.js"; + +export default { + name: 'AccountUserCheck', + components: { + FormRecipient + }, + data() { + return { + type: window.TYPEUSER, + phone: '', + isOnWhatsApp: null, + loading: false, + } + }, + computed: { + phone_id() { + return this.phone + this.type; + } + }, + methods: { + async openModal() { + this.handleReset(); + $('#modalUserCheck').modal('show'); + }, + isValidForm() { + return this.phone.trim() !== ''; + }, + async handleSubmit() { + if (!this.isValidForm() || this.loading) { + return; + } + try { + await this.submitApi(); + showSuccessInfo("Check completed") + } catch (err) { + showErrorInfo(err) + } + }, + async submitApi() { + this.loading = true; + try { + let response = await window.http.get(`/user/check?phone=${this.phone_id}`) + this.isOnWhatsApp = response.data.results.is_on_whatsapp; + } catch (error) { + if (error.response) { + throw new Error(error.response.data.message); + } + throw new Error(error.message); + } finally { + this.loading = false; + } + }, + handleReset() { + this.phone = ''; + this.isOnWhatsApp = null; + this.type = window.TYPEUSER; + } + }, + template: ` +
+
+ Account +
User Check
+
+ Check if a user is on WhatsApp +
+
+
+ + + ` +} \ No newline at end of file diff --git a/src/views/index.html b/src/views/index.html index 7635eea..58e266f 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -161,6 +161,7 @@ + @@ -225,6 +226,7 @@ import AccountUserInfo from "./components/AccountUserInfo.js"; import AccountPrivacy from "./components/AccountPrivacy.js"; import AccountContact from "./components/AccountContact.js"; + import AccountUserCheck from "./components/AccountUserCheck.js"; const showErrorInfo = (message) => { $('body').toast({ @@ -263,7 +265,7 @@ MessageDelete, MessageUpdate, MessageReact, MessageRevoke, GroupList, GroupCreate, GroupJoinWithLink, GroupAddParticipants, NewsletterList, - AccountAvatar, AccountUserInfo, AccountPrivacy, AccountChangeAvatar, AccountContact, AccountChangePushName + AccountAvatar, AccountUserInfo, AccountPrivacy, AccountChangeAvatar, AccountContact, AccountChangePushName, AccountUserCheck }, delimiters: ['[[', ']]'], data() {