├── Makefile ├── credentials.json ├── utils.go ├── scheduled_messages.json ├── .gitignore ├── docs ├── swagger.yaml ├── swagger.json └── docs.go ├── README.md ├── go.mod ├── whatsapp.go ├── instalar.md ├── main.go ├── LICENSE └── go.sum /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | swag init 3 | swag fmt 4 | go fmt ./... 5 | go build -------------------------------------------------------------------------------- /credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "usuario", 3 | "password": "contraseña" 4 | } -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "regexp" 4 | 5 | // IsNumeric checks if a string contains only numeric characters. 6 | func IsNumeric(s string) bool { 7 | // Compile the regex pattern 8 | re := regexp.MustCompile(`^[0-9]+$`) 9 | return re.MatchString(s) 10 | } 11 | -------------------------------------------------------------------------------- /scheduled_messages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "number": "0416 555 55 55", 4 | "message": "Hola, este es un mensaje programado.", 5 | "scheduled_at": "2025-01-25T15:00:00Z" 6 | }, 7 | { 8 | "number": "0414 555 55 55", 9 | "message": "Buenos días, este es otro mensaje programado.", 10 | "scheduled_at": "2025-01-25T16:00:00Z" 11 | } 12 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | filestore.db 27 | whatsapp-API 28 | credentials.txt 29 | -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: /api/v1 2 | definitions: 3 | main.MessageRequest: 4 | description: The message request body 5 | properties: 6 | message: 7 | type: string 8 | number: 9 | type: string 10 | type: object 11 | info: 12 | contact: {} 13 | description: Basic Auth 14 | title: Basic Auth 15 | paths: 16 | /recvMessage: 17 | get: 18 | consumes: 19 | - application/json 20 | description: Receive a message with the specified number 21 | parameters: 22 | - description: The number parameter 23 | in: query 24 | name: number 25 | required: true 26 | type: string 27 | produces: 28 | - application/json 29 | responses: 30 | "200": 31 | description: OK 32 | schema: 33 | additionalProperties: 34 | type: string 35 | type: object 36 | security: 37 | - BasicAuth: [] 38 | summary: Receive message 39 | /sendMessage: 40 | post: 41 | consumes: 42 | - application/json 43 | description: send message 44 | parameters: 45 | - description: Input message and number 46 | in: body 47 | name: request 48 | required: true 49 | schema: 50 | $ref: '#/definitions/main.MessageRequest' 51 | produces: 52 | - application/json 53 | responses: 54 | "200": 55 | description: OK 56 | schema: 57 | additionalProperties: 58 | type: string 59 | type: object 60 | security: 61 | - BasicAuth: [] 62 | summary: send message 63 | securityDefinitions: 64 | BasicAuth: 65 | type: basic 66 | swagger: "2.0" 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 WhatsApp API 2 | 3 | Este proyecto es una API REST que permite enviar y recibir mensajes de WhatsApp utilizando la biblioteca [whatsmeow](https://github.com/tulir/whatsmeow). 4 | 5 | ## 🌟 Características 6 | 7 | - **Envío de mensajes**: Envía mensajes de texto a números de WhatsApp específicos. 8 | - **Recepción de mensajes**: Simula la recepción de mensajes para pruebas. 9 | - **Autenticación básica**: Protege las rutas con autenticación básica (username y password). 10 | - **Integración con WhatsApp Web**: Inicia sesión en WhatsApp Web mediante un código QR. 11 | 12 | ## 🛠️ Tecnologías utilizadas 13 | 14 | - **Lenguaje**: Go (Golang) 15 | - **Biblioteca de WhatsApp**: [whatsmeow](https://github.com/tulir/whatsmeow) 16 | - **Framework web**: [Gin](https://github.com/gin-gonic/gin) 17 | - **Base de datos**: SQLite (para almacenar datos de sesión) 18 | 19 | ## 📝 Instrucciones 20 | 21 | 1. **Configura las credenciales**: 22 | - Crea un archivo `credentials.txt` con el formato `username:password`. 23 | 24 | 2. **Ejecuta el servidor**: 25 | - Ejecuta el programa con `go run main.go`. 26 | 27 | 3. **Envía mensajes**: 28 | - Usa la ruta `/api/v1/sendMessage` para enviar mensajes. 29 | 30 | 4. **Recibe mensajes**: 31 | - Usa la ruta `/api/v1/recvMessage` para simular la recepción de mensajes. 32 | 33 | ## 📄 Documentación 34 | 35 | - **Autenticación**: Usa el encabezado `Authorization: Basic base64(username:password)`. 36 | - **Ejemplo de solicitud**: 37 | ```json 38 | { 39 | "number": "1234567890", 40 | "message": "Hola, este es un mensaje de prueba" 41 | } 42 | 43 | | Aspecto | implementación con `whatsmeow` | API principal de Meta | 44 | |------------------------|----------------------------------|-----------------------| 45 | | **Enfoque** | WhatsApp Web | WhatsApp Business API | 46 | | **Autenticación** | Código QR | Token de acceso | 47 | | **Escalabilidad** | Limitada | Alta | 48 | | **Soporte** | Comunidad | Oficial | 49 | | **Limitaciones** | No oficial, no comercial | Aprobada por Meta | 50 | | **Uso** | Personas, proyectos pequeños | Negocios | 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module whatsapp-API 2 | 3 | go 1.23.2 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.10.0 7 | github.com/mattn/go-sqlite3 v1.14.24 8 | github.com/swaggo/files v1.0.1 9 | github.com/swaggo/gin-swagger v1.6.0 10 | github.com/swaggo/swag v1.16.4 11 | go.mau.fi/whatsmeow v0.0.0-20241019130619-e66372b56ef1 12 | google.golang.org/protobuf v1.35.1 13 | ) 14 | 15 | require ( 16 | filippo.io/edwards25519 v1.1.0 // indirect 17 | github.com/KyleBanks/depth v1.2.1 // indirect 18 | github.com/bytedance/sonic v1.11.6 // indirect 19 | github.com/bytedance/sonic/loader v0.1.1 // indirect 20 | github.com/cloudwego/base64x v0.1.4 // indirect 21 | github.com/cloudwego/iasm v0.2.0 // indirect 22 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 23 | github.com/gin-contrib/sse v0.1.0 // indirect 24 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 25 | github.com/go-openapi/jsonreference v0.21.0 // indirect 26 | github.com/go-openapi/spec v0.21.0 // indirect 27 | github.com/go-openapi/swag v0.23.0 // indirect 28 | github.com/go-playground/locales v0.14.1 // indirect 29 | github.com/go-playground/universal-translator v0.18.1 // indirect 30 | github.com/go-playground/validator/v10 v10.20.0 // indirect 31 | github.com/goccy/go-json v0.10.2 // indirect 32 | github.com/google/uuid v1.6.0 // indirect 33 | github.com/gorilla/websocket v1.5.0 // indirect 34 | github.com/josharian/intern v1.0.0 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 37 | github.com/leodido/go-urn v1.4.0 // indirect 38 | github.com/mailru/easyjson v0.7.7 // indirect 39 | github.com/mattn/go-colorable v0.1.13 // indirect 40 | github.com/mattn/go-isatty v0.0.20 // indirect 41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 42 | github.com/modern-go/reflect2 v1.0.2 // indirect 43 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 44 | github.com/rs/zerolog v1.33.0 // indirect 45 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 46 | github.com/ugorji/go/codec v1.2.12 // indirect 47 | go.mau.fi/libsignal v0.1.1 // indirect 48 | go.mau.fi/util v0.8.0 // indirect 49 | golang.org/x/arch v0.8.0 // indirect 50 | golang.org/x/crypto v0.28.0 // indirect 51 | golang.org/x/net v0.30.0 // indirect 52 | golang.org/x/sys v0.26.0 // indirect 53 | golang.org/x/text v0.19.0 // indirect 54 | golang.org/x/tools v0.26.0 // indirect 55 | gopkg.in/yaml.v3 v3.0.1 // indirect 56 | ) 57 | -------------------------------------------------------------------------------- /whatsapp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | _ "github.com/mattn/go-sqlite3" 9 | "go.mau.fi/whatsmeow" 10 | waProto "go.mau.fi/whatsmeow/binary/proto" 11 | "go.mau.fi/whatsmeow/store/sqlstore" 12 | "go.mau.fi/whatsmeow/types" 13 | "go.mau.fi/whatsmeow/types/events" 14 | waLog "go.mau.fi/whatsmeow/util/log" 15 | "google.golang.org/protobuf/proto" 16 | ) 17 | 18 | func eventHandler(evt interface{}) { 19 | switch v := evt.(type) { 20 | case *events.Message: 21 | fmt.Println("Received a message!", v.Message.GetConversation()) 22 | } 23 | } 24 | 25 | // Function to send a message 26 | func sendMessageWA(client *whatsmeow.Client, recipient string, messageText string) error { 27 | // Create a message 28 | message := &waProto.Message{ 29 | Conversation: proto.String(messageText), 30 | } 31 | 32 | log.Println("Sending to " + recipient + " with message: " + messageText) 33 | // Convert the recipient number into the correct WhatsApp format (JID) 34 | jid, err := types.ParseJID(recipient) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | // Send the message 40 | _, err = client.SendMessage(context.Background(), jid, message) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func LoginWhatsapp() *whatsmeow.Client { 49 | dbLog := waLog.Stdout("Database", "DEBUG", true) 50 | container, err := sqlstore.New("sqlite3", "file:filestore.db?_foreign_keys=on", dbLog) 51 | if err != nil { 52 | panic(err) 53 | } 54 | deviceStore, err := container.GetFirstDevice() 55 | if err != nil { 56 | panic(err) 57 | } 58 | clientLog := waLog.Stdout("Client", "DEBUG", true) 59 | client := whatsmeow.NewClient(deviceStore, clientLog) 60 | client.AddEventHandler(eventHandler) 61 | 62 | if client.Store.ID == nil { 63 | // No ID stored, new login 64 | qrChan, _ := client.GetQRChannel(context.Background()) 65 | err = client.Connect() 66 | if err != nil { 67 | panic(err) 68 | } 69 | for evt := range qrChan { 70 | if evt.Event == "code" { 71 | // Render the QR code here 72 | // e.g. qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout) 73 | // or just manually `echo 2@... | qrencode -t ansiutf8` in a terminal 74 | fmt.Println("QR code:", evt.Code) 75 | } else { 76 | fmt.Println("Login event:", evt.Event) 77 | } 78 | } 79 | } else { 80 | // Already logged in, just connect 81 | err = client.Connect() 82 | if err != nil { 83 | panic(err) 84 | } 85 | } 86 | 87 | return client 88 | } 89 | -------------------------------------------------------------------------------- /instalar.md: -------------------------------------------------------------------------------- 1 | ```markdown 2 | # Pasos de instalación para Evolution API (WhatsApp API) 3 | 4 | ## 📋 Requisitos previos 5 | 1. Instalar **Go (Golang)**: 6 | - Descarga oficial: [https://go.dev/dl/](https://go.dev/dl/) 7 | - Verifica la instalación: 8 | ```bash 9 | go version 10 | ``` 11 | 12 | 2. Tener una **cuenta de WhatsApp activa** en tu teléfono móvil 13 | 14 | ## 🚀 Instalación y configuración 15 | 16 | ### 1. Clonar el repositorio 17 | ```bash 18 | git clone https://github.com/0xC1pher/whatsapp-api.git 19 | cd whatsapp-api 20 | ``` 21 | 22 | ### 2. Instalar dependencias 23 | ```bash 24 | go mod tidy 25 | ``` 26 | 27 | ### 3. Ejecutar la API 28 | ```bash 29 | go run main.go 30 | ``` 31 | 32 | ### 4. Vincular WhatsApp (Primer uso) 33 | 1. Al iniciar, la API generará un **código QR** en la terminal o en un archivo `qr.png` 34 | 2. En tu teléfono: 35 | - Abre WhatsApp 36 | - Toca ⋮ (Menú) → **Dispositivos vinculados** → **Vincular un dispositivo** 37 | - Escanea el código QR mostrado por la API 38 | 39 | ### 5. Usar la API (Ejemplo de envío) 40 | 1. La API expone endpoints como `POST /send-message` 41 | 2. Ejemplo de solicitud usando `curl`: 42 | ```bash 43 | curl -X POST http://localhost:8080/send-message \ 44 | -H "Content-Type: application/json" \ 45 | -d '{ 46 | "phone": "5491112345678", 47 | "message": "Hola API" 48 | }' 49 | ``` 50 | 51 | ### 6. Automatizar envíos 52 | Crea un script que recorra una lista de números: 53 | ```python 54 | import requests 55 | 56 | numeros = ["5491112345678", "5491123456789"] 57 | api_url = "http://localhost:8080/send-message" 58 | 59 | for numero in numeros: 60 | payload = { 61 | "phone": numero, 62 | "message": "Mensaje automatizado" 63 | } 64 | response = requests.post(api_url, json=payload) 65 | print(f"Enviado a {numero}: {response.status_code}") 66 | ``` 67 | 68 | ## ⚙️ Configuración avanzada 69 | - **Variables de entorno**: Crea un archivo `.env` para personalizar: 70 | ``` 71 | PORT=3000 72 | SESSION_NAME=my_session 73 | LOG_LEVEL=debug 74 | ``` 75 | - **Persistencia**: Las sesiones se guardan en `./sessions/` (no eliminar esta carpeta) 76 | 77 | ## 🚨 Notas importantes 78 | - Mantén tu **teléfono con conexión a Internet** mientras uses la API 79 | - Si ves "dispositivo vinculado" en WhatsApp, **no pulses desconectar** 80 | - Para reiniciar sesión: Elimina el archivo de sesión correspondiente en `./sessions/` 81 | ``` 82 | 83 | ### 📌 Observaciones clave: 84 | 1. **Requisito WhatsApp**: Necesitas una cuenta de WhatsApp real para vincular 85 | 2. **Funcionamiento**: La API actúa como puente entre tu servidor y WhatsApp Web 86 | 3. **Seguridad**: Nunca compartas los archivos de sesión (`session.gob`) 87 | 4. **Alternativa oficial**: Para uso profesional considera [WhatsApp Business API](https://developers.facebook.com/docs/whatsapp/cloud-api) 88 | -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "Basic Auth", 5 | "title": "Basic Auth", 6 | "contact": {} 7 | }, 8 | "basePath": "/api/v1", 9 | "paths": { 10 | "/recvMessage": { 11 | "get": { 12 | "security": [ 13 | { 14 | "BasicAuth": [] 15 | } 16 | ], 17 | "description": "Receive a message with the specified number", 18 | "consumes": [ 19 | "application/json" 20 | ], 21 | "produces": [ 22 | "application/json" 23 | ], 24 | "summary": "Receive message", 25 | "parameters": [ 26 | { 27 | "type": "string", 28 | "description": "The number parameter", 29 | "name": "number", 30 | "in": "query", 31 | "required": true 32 | } 33 | ], 34 | "responses": { 35 | "200": { 36 | "description": "OK", 37 | "schema": { 38 | "type": "object", 39 | "additionalProperties": { 40 | "type": "string" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | }, 47 | "/sendMessage": { 48 | "post": { 49 | "security": [ 50 | { 51 | "BasicAuth": [] 52 | } 53 | ], 54 | "description": "send message", 55 | "consumes": [ 56 | "application/json" 57 | ], 58 | "produces": [ 59 | "application/json" 60 | ], 61 | "summary": "send message", 62 | "parameters": [ 63 | { 64 | "description": "Input message and number", 65 | "name": "request", 66 | "in": "body", 67 | "required": true, 68 | "schema": { 69 | "$ref": "#/definitions/main.MessageRequest" 70 | } 71 | } 72 | ], 73 | "responses": { 74 | "200": { 75 | "description": "OK", 76 | "schema": { 77 | "type": "object", 78 | "additionalProperties": { 79 | "type": "string" 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | }, 87 | "definitions": { 88 | "main.MessageRequest": { 89 | "description": "The message request body", 90 | "type": "object", 91 | "properties": { 92 | "message": { 93 | "type": "string" 94 | }, 95 | "number": { 96 | "type": "string" 97 | } 98 | } 99 | } 100 | }, 101 | "securityDefinitions": { 102 | "BasicAuth": { 103 | "type": "basic" 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs Code generated by swaggo/swag. DO NOT EDIT 2 | package docs 3 | 4 | import "github.com/swaggo/swag" 5 | 6 | const docTemplate = `{ 7 | "schemes": {{ marshal .Schemes }}, 8 | "swagger": "2.0", 9 | "info": { 10 | "description": "{{escape .Description}}", 11 | "title": "{{.Title}}", 12 | "contact": {}, 13 | "version": "{{.Version}}" 14 | }, 15 | "host": "{{.Host}}", 16 | "basePath": "{{.BasePath}}", 17 | "paths": { 18 | "/recvMessage": { 19 | "get": { 20 | "security": [ 21 | { 22 | "BasicAuth": [] 23 | } 24 | ], 25 | "description": "Receive a message with the specified number", 26 | "consumes": [ 27 | "application/json" 28 | ], 29 | "produces": [ 30 | "application/json" 31 | ], 32 | "summary": "Receive message", 33 | "parameters": [ 34 | { 35 | "type": "string", 36 | "description": "The number parameter", 37 | "name": "number", 38 | "in": "query", 39 | "required": true 40 | } 41 | ], 42 | "responses": { 43 | "200": { 44 | "description": "OK", 45 | "schema": { 46 | "type": "object", 47 | "additionalProperties": { 48 | "type": "string" 49 | } 50 | } 51 | } 52 | } 53 | } 54 | }, 55 | "/sendMessage": { 56 | "post": { 57 | "security": [ 58 | { 59 | "BasicAuth": [] 60 | } 61 | ], 62 | "description": "send message", 63 | "consumes": [ 64 | "application/json" 65 | ], 66 | "produces": [ 67 | "application/json" 68 | ], 69 | "summary": "send message", 70 | "parameters": [ 71 | { 72 | "description": "Input message and number", 73 | "name": "request", 74 | "in": "body", 75 | "required": true, 76 | "schema": { 77 | "$ref": "#/definitions/main.MessageRequest" 78 | } 79 | } 80 | ], 81 | "responses": { 82 | "200": { 83 | "description": "OK", 84 | "schema": { 85 | "type": "object", 86 | "additionalProperties": { 87 | "type": "string" 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | }, 95 | "definitions": { 96 | "main.MessageRequest": { 97 | "description": "The message request body", 98 | "type": "object", 99 | "properties": { 100 | "message": { 101 | "type": "string" 102 | }, 103 | "number": { 104 | "type": "string" 105 | } 106 | } 107 | } 108 | }, 109 | "securityDefinitions": { 110 | "BasicAuth": { 111 | "type": "basic" 112 | } 113 | } 114 | }` 115 | 116 | // SwaggerInfo holds exported Swagger Info so clients can modify it 117 | var SwaggerInfo = &swag.Spec{ 118 | Version: "", 119 | Host: "", 120 | BasePath: "/api/v1", 121 | Schemes: []string{}, 122 | Title: "Basic Auth", 123 | Description: "Basic Auth", 124 | InfoInstanceName: "swagger", 125 | SwaggerTemplate: docTemplate, 126 | LeftDelim: "{{", 127 | RightDelim: "}}", 128 | } 129 | 130 | func init() { 131 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 132 | } 133 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "os" 12 | "strconv" 13 | "strings" 14 | "time" 15 | 16 | "github.com/gin-gonic/gin" 17 | _ "github.com/mattn/go-sqlite3" 18 | "go.mau.fi/whatsmeow" 19 | waProto "go.mau.fi/whatsmeow/binary/proto" 20 | "go.mau.fi/whatsmeow/store/sqlstore" 21 | "go.mau.fi/whatsmeow/types" 22 | "go.mau.fi/whatsmeow/types/events" 23 | waLog "go.mau.fi/whatsmeow/util/log" 24 | "google.golang.org/protobuf/proto" 25 | ) 26 | 27 | // MessageRequest representa la estructura del cuerpo de la solicitud 28 | type MessageRequest struct { 29 | Number string `json:"number"` 30 | Message string `json:"message"` 31 | } 32 | 33 | // Credentials estructura para almacenar credenciales 34 | type Credentials struct { 35 | Username string `json:"username"` 36 | Password string `json:"password"` 37 | } 38 | 39 | // ScheduledMessage representa un mensaje programado 40 | type ScheduledMessage struct { 41 | Number string `json:"number"` 42 | Message string `json:"message"` 43 | ScheduledAt time.Time `json:"scheduled_at"` 44 | } 45 | 46 | // Variables globales 47 | var validCredentials Credentials 48 | var scheduledMessages []ScheduledMessage 49 | 50 | // LoadCredentialsFromJSON carga credenciales desde un archivo JSON 51 | func LoadCredentialsFromJSON(filePath string) (Credentials, error) { 52 | file, err := os.Open(filePath) 53 | if err != nil { 54 | return Credentials{}, err 55 | } 56 | defer file.Close() 57 | 58 | var credentials Credentials 59 | err = json.NewDecoder(file).Decode(&credentials) 60 | return credentials, err 61 | } 62 | 63 | // SaveCredentialsToJSON guarda credenciales en un archivo JSON 64 | func SaveCredentialsToJSON(filePath string, credentials Credentials) error { 65 | file, err := os.Create(filePath) 66 | if err != nil { 67 | return err 68 | } 69 | defer file.Close() 70 | 71 | encoder := json.NewEncoder(file) 72 | encoder.SetIndent("", " ") 73 | return encoder.Encode(credentials) 74 | } 75 | 76 | // LoadScheduledMessages carga mensajes programados desde un archivo JSON 77 | func LoadScheduledMessages(filePath string) ([]ScheduledMessage, error) { 78 | file, err := os.Open(filePath) 79 | if err != nil { 80 | return nil, err 81 | } 82 | defer file.Close() 83 | 84 | var messages []ScheduledMessage 85 | err = json.NewDecoder(file).Decode(&messages) 86 | return messages, err 87 | } 88 | 89 | // SaveScheduledMessages guarda mensajes programados en un archivo JSON 90 | func SaveScheduledMessages(filePath string, messages []ScheduledMessage) error { 91 | file, err := os.Create(filePath) 92 | if err != nil { 93 | return err 94 | } 95 | defer file.Close() 96 | 97 | encoder := json.NewEncoder(file) 98 | encoder.SetIndent("", " ") 99 | return encoder.Encode(messages) 100 | } 101 | 102 | // SendMessage maneja el envío de mensajes 103 | func SendMessage(g *gin.Context, client *whatsmeow.Client) { 104 | // Verificar el encabezado de autorización 105 | if !validateBasicAuth(g) { 106 | return 107 | } 108 | 109 | var jsonBody MessageRequest 110 | 111 | if err := g.ShouldBindJSON(&jsonBody); err != nil { 112 | g.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 113 | return 114 | } 115 | 116 | if !IsNumeric(jsonBody.Number) { 117 | g.JSON(http.StatusBadRequest, gin.H{"error": "not a number"}) 118 | return 119 | } 120 | 121 | err := sendMessageWA(client, fmt.Sprintf("%v@s.whatsapp.net", jsonBody.Number), jsonBody.Message) 122 | if err != nil { 123 | g.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 124 | return 125 | } 126 | 127 | g.JSON(http.StatusOK, gin.H{"response": "Sent to " + jsonBody.Number + ": " + jsonBody.Message}) 128 | } 129 | 130 | // RecvMessage maneja la recepción de mensajes 131 | func RecvMessage(g *gin.Context) { 132 | // Verificar el encabezado de autorización 133 | if !validateBasicAuth(g) { 134 | return 135 | } 136 | 137 | number := g.Query("number") // Recuperar el parámetro "number" de la consulta 138 | 139 | if number == "" { 140 | g.JSON(http.StatusBadRequest, gin.H{"error": "number parameter is required"}) 141 | return 142 | } 143 | 144 | g.JSON(http.StatusOK, gin.H{"message": "Received number: " + number}) 145 | } 146 | 147 | // validateBasicAuth verifica el encabezado de autorización para Basic Auth 148 | func validateBasicAuth(g *gin.Context) bool { 149 | authHeader := g.GetHeader("Authorization") 150 | if authHeader == "" { 151 | g.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is missing"}) 152 | return false 153 | } 154 | 155 | // Extraer el token del encabezado 156 | token := strings.TrimPrefix(authHeader, "Basic ") 157 | decoded, err := base64.StdEncoding.DecodeString(token) 158 | if err != nil { 159 | g.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization header"}) 160 | return false 161 | } 162 | 163 | // Dividir la cadena decodificada en usuario y contraseña 164 | credentials := strings.Split(string(decoded), ":") 165 | if len(credentials) != 2 || !validateCredentials(credentials[0], credentials[1]) { 166 | g.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"}) 167 | return false 168 | } 169 | 170 | return true 171 | } 172 | 173 | // validateCredentials verifica las credenciales proporcionadas 174 | func validateCredentials(username, password string) bool { 175 | return username == validCredentials.Username && password == validCredentials.Password 176 | } 177 | 178 | // IsNumeric verifica si una cadena es un número válido 179 | func IsNumeric(s string) bool { 180 | _, err := strconv.ParseFloat(s, 64) 181 | return err == nil 182 | } 183 | 184 | // sendMessageWA envía un mensaje de WhatsApp 185 | func sendMessageWA(client *whatsmeow.Client, recipient string, messageText string) error { 186 | message := &waProto.Message{ 187 | Conversation: proto.String(messageText), 188 | } 189 | 190 | jid, err := types.ParseJID(recipient) 191 | if err != nil { 192 | return err 193 | } 194 | 195 | _, err = client.SendMessage(context.Background(), jid, message) 196 | return err 197 | } 198 | 199 | // eventHandler maneja eventos de WhatsApp 200 | func eventHandler(evt interface{}) { 201 | switch v := evt.(type) { 202 | case *events.Message: 203 | fmt.Println("Received a message!", v.Message.GetConversation()) 204 | } 205 | } 206 | 207 | // LoginWhatsapp inicia sesión en WhatsApp Web 208 | func LoginWhatsapp() *whatsmeow.Client { 209 | dbLog := waLog.Stdout("Database", "DEBUG", true) 210 | container, err := sqlstore.New("sqlite3", "file:filestore.db?_foreign_keys=on", dbLog) 211 | if err != nil { 212 | panic(err) 213 | } 214 | deviceStore, err := container.GetFirstDevice() 215 | if err != nil { 216 | panic(err) 217 | } 218 | clientLog := waLog.Stdout("Client", "DEBUG", true) 219 | client := whatsmeow.NewClient(deviceStore, clientLog) 220 | client.AddEventHandler(eventHandler) 221 | 222 | if client.Store.ID == nil { 223 | // No ID almacenado, nuevo inicio de sesión 224 | qrChan, _ := client.GetQRChannel(context.Background()) 225 | err = client.Connect() 226 | if err != nil { 227 | panic(err) 228 | } 229 | for evt := range qrChan { 230 | if evt.Event == "code" { 231 | fmt.Println("QR code:", evt.Code) 232 | } else { 233 | fmt.Println("Login event:", evt.Event) 234 | } 235 | } 236 | } else { 237 | // Ya iniciado sesión, solo conectar 238 | err = client.Connect() 239 | if err != nil { 240 | panic(err) 241 | } 242 | } 243 | 244 | return client 245 | } 246 | 247 | // ScheduleMessagesProgrammed programa y envía mensajes programados 248 | func ScheduleMessagesProgrammed(client *whatsmeow.Client) { 249 | for _, msg := range scheduledMessages { 250 | time.Sleep(time.Until(msg.ScheduledAt)) // Esperar hasta la hora programada 251 | err := sendMessageWA(client, msg.Number, msg.Message) 252 | if err != nil { 253 | log.Printf("Error sending scheduled message: %v", err) 254 | } else { 255 | log.Printf("Scheduled message sent to %s: %s", msg.Number, msg.Message) 256 | } 257 | } 258 | } 259 | 260 | func main() { 261 | port := "45981" 262 | 263 | // Cargar credenciales desde un archivo JSON 264 | credentials, err := LoadCredentialsFromJSON("credentials.json") 265 | if err != nil { 266 | panic("Failed to load credentials: " + err.Error()) 267 | } 268 | validCredentials = credentials 269 | 270 | // Cargar mensajes programados desde un archivo JSON 271 | scheduledMessages, err = LoadScheduledMessages("scheduled_messages.json") 272 | if err != nil { 273 | panic("Failed to load scheduled messages: " + err.Error()) 274 | } 275 | 276 | gin.SetMode(gin.ReleaseMode) 277 | clientWhatsapp := LoginWhatsapp() 278 | 279 | // Iniciar el envío de mensajes programados 280 | go ScheduleMessagesProgrammed(clientWhatsapp) 281 | 282 | r := gin.Default() 283 | v1 := r.Group("/api/v1") 284 | v1.POST("/sendMessage", func(c *gin.Context) { 285 | SendMessage(c, clientWhatsapp) 286 | }) 287 | v1.GET("/recvMessage", RecvMessage) 288 | log.Println("Running on port " + port) 289 | r.Run(":" + port) 290 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 3 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 4 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 5 | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= 6 | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 7 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= 8 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 9 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 10 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 11 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 12 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 13 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 18 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 19 | github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= 20 | github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= 21 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 22 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 23 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 24 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 25 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 26 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 27 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 28 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 29 | github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= 30 | github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= 31 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 32 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 33 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 34 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 35 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 36 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 37 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 38 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 39 | github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= 40 | github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 41 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 42 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 43 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 44 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 45 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 46 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 47 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 48 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 49 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 50 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 51 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 52 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 53 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 54 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 55 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 56 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 57 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 58 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 59 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 60 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 61 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 62 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 63 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 64 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 65 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 66 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 67 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 68 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 69 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 70 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 71 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 72 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 73 | github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= 74 | github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 75 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 76 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 77 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 78 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 79 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 80 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 81 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 82 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 83 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 84 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 85 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 86 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 87 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 88 | github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= 89 | github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 90 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 91 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 92 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 93 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 94 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 95 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 96 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 97 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 98 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 99 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 100 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 101 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 102 | github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= 103 | github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= 104 | github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= 105 | github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= 106 | github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= 107 | github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= 108 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 109 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 110 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 111 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 112 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 113 | go.mau.fi/libsignal v0.1.1 h1:m/0PGBh4QKP/I1MQ44ti4C0fMbLMuHb95cmDw01FIpI= 114 | go.mau.fi/libsignal v0.1.1/go.mod h1:QLs89F/OA3ThdSL2Wz2p+o+fi8uuQUz0e1BRa6ExdBw= 115 | go.mau.fi/util v0.8.0 h1:MiSny8jgQq4XtCLAT64gDJhZVhqiDeMVIEBDFVw+M0g= 116 | go.mau.fi/util v0.8.0/go.mod h1:1Ixb8HWoVbl3rT6nAX6nV4iMkzn7KU/KXwE0Rn5RmsQ= 117 | go.mau.fi/whatsmeow v0.0.0-20241019130619-e66372b56ef1 h1:vcIEXfF/Yr/4jUUdoOm4J+oubV3ctp3wZpD9tv7iLgA= 118 | go.mau.fi/whatsmeow v0.0.0-20241019130619-e66372b56ef1/go.mod h1:UvaXcdb8y5Mryj2LSXAMw7u4/exnWJIXn8Gvpmf6ndI= 119 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 120 | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= 121 | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 122 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 123 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 124 | golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= 125 | golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 126 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 127 | golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= 128 | golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 129 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 130 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 131 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 132 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 133 | golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= 134 | golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 135 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 136 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 137 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 138 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 139 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 140 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 142 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 143 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 144 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 145 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 146 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 147 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 148 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 149 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 150 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 151 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 152 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 153 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 154 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 155 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 156 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 157 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 158 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 159 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 160 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 161 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 162 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 163 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 164 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 165 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 166 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 167 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 168 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 169 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 170 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 171 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 172 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 173 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 174 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 175 | --------------------------------------------------------------------------------