feat: Go backend, enhanced search, new widgets, Docker deploy
Major changes: - Add Go backend (backend/) with microservices architecture - Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler, proxy-manager, media-search, fastClassifier, language detection - New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard, UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions - Improved discover-svc with discover-db integration - Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md) - Library-svc: project_id schema migration - Remove deprecated finance-svc and travel-svc - Localization improvements across services Made-with: Cursor
This commit is contained in:
263
backend/internal/computer/connectors/telegram.go
Normal file
263
backend/internal/computer/connectors/telegram.go
Normal file
@@ -0,0 +1,263 @@
|
||||
package connectors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TelegramConfig struct {
|
||||
BotToken string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type TelegramConnector struct {
|
||||
cfg TelegramConfig
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewTelegramConnector(cfg TelegramConfig) *TelegramConnector {
|
||||
timeout := cfg.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
return &TelegramConnector{
|
||||
cfg: cfg,
|
||||
client: &http.Client{
|
||||
Timeout: timeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) ID() string {
|
||||
return "telegram"
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) Name() string {
|
||||
return "Telegram"
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) Description() string {
|
||||
return "Send messages via Telegram Bot API"
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) GetActions() []Action {
|
||||
return []Action{
|
||||
{
|
||||
Name: "send_message",
|
||||
Description: "Send a text message",
|
||||
Schema: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"chat_id": map[string]interface{}{"type": "string", "description": "Chat ID or @username"},
|
||||
"text": map[string]interface{}{"type": "string", "description": "Message text"},
|
||||
"parse_mode": map[string]interface{}{"type": "string", "enum": []string{"HTML", "Markdown", "MarkdownV2"}},
|
||||
},
|
||||
},
|
||||
Required: []string{"chat_id", "text"},
|
||||
},
|
||||
{
|
||||
Name: "send_document",
|
||||
Description: "Send a document/file",
|
||||
Schema: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"chat_id": map[string]interface{}{"type": "string", "description": "Chat ID"},
|
||||
"document": map[string]interface{}{"type": "string", "description": "File path or URL"},
|
||||
"caption": map[string]interface{}{"type": "string", "description": "Document caption"},
|
||||
},
|
||||
},
|
||||
Required: []string{"chat_id", "document"},
|
||||
},
|
||||
{
|
||||
Name: "send_photo",
|
||||
Description: "Send a photo",
|
||||
Schema: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"chat_id": map[string]interface{}{"type": "string", "description": "Chat ID"},
|
||||
"photo": map[string]interface{}{"type": "string", "description": "Photo URL or file_id"},
|
||||
"caption": map[string]interface{}{"type": "string", "description": "Photo caption"},
|
||||
},
|
||||
},
|
||||
Required: []string{"chat_id", "photo"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) Validate(params map[string]interface{}) error {
|
||||
if _, ok := params["chat_id"]; !ok {
|
||||
return errors.New("'chat_id' is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
|
||||
switch action {
|
||||
case "send_message":
|
||||
return t.sendMessage(ctx, params)
|
||||
case "send_document":
|
||||
return t.sendDocument(ctx, params)
|
||||
case "send_photo":
|
||||
return t.sendPhoto(ctx, params)
|
||||
default:
|
||||
return nil, errors.New("unknown action: " + action)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) sendMessage(ctx context.Context, params map[string]interface{}) (interface{}, error) {
|
||||
chatID := params["chat_id"].(string)
|
||||
text := params["text"].(string)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"chat_id": chatID,
|
||||
"text": text,
|
||||
}
|
||||
|
||||
if parseMode, ok := params["parse_mode"].(string); ok {
|
||||
payload["parse_mode"] = parseMode
|
||||
}
|
||||
|
||||
return t.apiCall(ctx, "sendMessage", payload)
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) sendDocument(ctx context.Context, params map[string]interface{}) (interface{}, error) {
|
||||
chatID := params["chat_id"].(string)
|
||||
document := params["document"].(string)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"chat_id": chatID,
|
||||
"document": document,
|
||||
}
|
||||
|
||||
if caption, ok := params["caption"].(string); ok {
|
||||
payload["caption"] = caption
|
||||
}
|
||||
|
||||
return t.apiCall(ctx, "sendDocument", payload)
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) sendPhoto(ctx context.Context, params map[string]interface{}) (interface{}, error) {
|
||||
chatID := params["chat_id"].(string)
|
||||
photo := params["photo"].(string)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"chat_id": chatID,
|
||||
"photo": photo,
|
||||
}
|
||||
|
||||
if caption, ok := params["caption"].(string); ok {
|
||||
payload["caption"] = caption
|
||||
}
|
||||
|
||||
return t.apiCall(ctx, "sendPhoto", payload)
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) apiCall(ctx context.Context, method string, payload map[string]interface{}) (interface{}, error) {
|
||||
url := fmt.Sprintf("https://api.telegram.org/bot%s/%s", t.cfg.BotToken, method)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := t.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok, exists := result["ok"].(bool); exists && !ok {
|
||||
desc := "unknown error"
|
||||
if d, exists := result["description"].(string); exists {
|
||||
desc = d
|
||||
}
|
||||
return result, errors.New("Telegram API error: " + desc)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) SendFileFromBytes(ctx context.Context, chatID string, filename string, content []byte, caption string) (interface{}, error) {
|
||||
url := fmt.Sprintf("https://api.telegram.org/bot%s/sendDocument", t.cfg.BotToken)
|
||||
|
||||
var b bytes.Buffer
|
||||
w := multipart.NewWriter(&b)
|
||||
|
||||
w.WriteField("chat_id", chatID)
|
||||
|
||||
if caption != "" {
|
||||
w.WriteField("caption", caption)
|
||||
}
|
||||
|
||||
fw, err := w.CreateFormFile("document", filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fw.Write(content)
|
||||
|
||||
w.Close()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, &b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", w.FormDataContentType())
|
||||
|
||||
resp, err := t.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t *TelegramConnector) GetChatID(chatIDOrUsername interface{}) string {
|
||||
switch v := chatIDOrUsername.(type) {
|
||||
case string:
|
||||
return v
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
case float64:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user