No known key found for this signature in database
GPG Key ID: 66C92B1C5B475512
42 changed files with 1335 additions and 371 deletions
-
122.cursor/rules/project-structure.mdc
-
10.dockerignore
-
2docker/golang.Dockerfile
-
209docs/openapi.yaml
-
148readme.md
-
2src/.env.example
-
62src/cmd/mcp.go
-
115src/cmd/rest.go
-
168src/cmd/root.go
-
5src/config/settings.go
-
2src/domains/app/app.go
-
2src/domains/group/group.go
-
2src/domains/message/message.go
-
2src/domains/newsletter/newsletter.go
-
2src/domains/send/send.go
-
8src/domains/user/account.go
-
3src/domains/user/user.go
-
22src/go.mod
-
102src/go.sum
-
78src/infrastructure/whatsapp/init.go
-
5src/infrastructure/whatsapp/utils.go
-
16src/infrastructure/whatsapp/webhook.go
-
338src/ui/mcp/send.go
-
5src/ui/rest/app.go
-
6src/ui/rest/group.go
-
7src/ui/rest/helpers/common.go
-
0src/ui/rest/helpers/flushChatCsv.go
-
6src/ui/rest/message.go
-
0src/ui/rest/middleware/basicauth.go
-
0src/ui/rest/middleware/recovery.go
-
4src/ui/rest/newsletter.go
-
6src/ui/rest/send.go
-
23src/ui/rest/user.go
-
2src/ui/websocket/websocket.go
-
16src/usecase/app.go
-
24src/usecase/group.go
-
10src/usecase/message.go
-
13src/usecase/newsletter.go
-
10src/usecase/send.go
-
42src/usecase/user.go
-
95src/views/components/AccountUserCheck.js
-
4src/views/index.html
@ -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 |
|||
@ -0,0 +1,10 @@ |
|||
.idea |
|||
.vscode |
|||
.git |
|||
.gitignore |
|||
.env |
|||
.env.local |
|||
.env.development |
|||
.env.test |
|||
.env.production |
|||
docker |
|||
@ -0,0 +1,62 @@ |
|||
package cmd |
|||
|
|||
import ( |
|||
"fmt" |
|||
"log" |
|||
|
|||
"github.com/aldinokemal/go-whatsapp-web-multidevice/config" |
|||
"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) { |
|||
// 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) |
|||
} |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
package cmd |
|||
|
|||
import ( |
|||
"fmt" |
|||
"log" |
|||
"net/http" |
|||
"strings" |
|||
|
|||
"github.com/aldinokemal/go-whatsapp-web-multidevice/config" |
|||
"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) { |
|||
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 <user>:<secret>") |
|||
} |
|||
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()) |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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: `
|
|||
<div class="olive card" @click="openModal" style="cursor: pointer;"> |
|||
<div class="content"> |
|||
<a class="ui olive right ribbon label">Account</a> |
|||
<div class="header">User Check</div> |
|||
<div class="description"> |
|||
Check if a user is on WhatsApp |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="ui small modal" id="modalUserCheck"> |
|||
<i class="close icon"></i> |
|||
<div class="header"> |
|||
Check if User is on WhatsApp |
|||
</div> |
|||
<div class="content"> |
|||
<form class="ui form"> |
|||
<FormRecipient v-model:type="type" v-model:phone="phone"/> |
|||
<button type="button" class="ui primary button" :class="{'loading': loading, 'disabled': !this.isValidForm() || this.loading}" |
|||
@click.prevent="handleSubmit"> |
|||
Check |
|||
</button> |
|||
</form> |
|||
|
|||
<div v-if="isOnWhatsApp !== null" class="ui message" :class="isOnWhatsApp ? 'positive' : 'negative'"> |
|||
<div class="header"> |
|||
<i :class="isOnWhatsApp ? 'check circle icon' : 'times circle icon'"></i> |
|||
{{ isOnWhatsApp ? 'User is on WhatsApp' : 'User is not on WhatsApp' }} |
|||
</div> |
|||
<p>Phone: {{ phone_id }}</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
`
|
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue