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))