package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "strconv" ) const maxAPIBase = "https://platform-api.max.ru" type maxUpdatesResponse struct { Updates []maxUpdate `json:"updates"` Marker *int64 `json:"marker"` } type maxUpdate struct { UpdateType string `json:"update_type"` Timestamp int64 `json:"timestamp"` Message *maxMessage `json:"message"` } type maxMessage struct { Sender *maxUser `json:"sender"` Recipient *maxRecipient `json:"recipient"` Body *maxMessageBody `json:"body"` } type maxUser struct { UserID int64 `json:"user_id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Username string `json:"username"` IsBot bool `json:"is_bot"` } type maxRecipient struct { ChatID int64 `json:"chat_id"` ChatType string `json:"chat_type"` UserID int64 `json:"user_id"` } type maxMessageBody struct { Text string `json:"text"` Attachments []maxAttachment `json:"attachments"` } type maxAttachment struct { Type string `json:"type"` Payload json.RawMessage `json:"payload"` } type maxAttachmentURL struct { URL string `json:"url"` } func (b *Bridge) maxRequest(method, path string, query url.Values, body any) ([]byte, error) { if b.MAXToken == "" { return nil, fmt.Errorf("MAX_TOKEN не задан") } reqURL := maxAPIBase + path if len(query) > 0 { reqURL += "?" + query.Encode() } var bodyReader io.Reader if body != nil { data, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(data) } req, err := http.NewRequest(method, reqURL, bodyReader) if err != nil { return nil, err } req.Header.Set("Authorization", b.MAXToken) if body != nil { req.Header.Set("Content-Type", "application/json") } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("MAX API %s %s: %d %s", method, path, resp.StatusCode, string(respBody)) } return respBody, nil } func (b *Bridge) SendToMAX(chatID int64, text string) error { query := url.Values{"chat_id": {strconv.FormatInt(chatID, 10)}} _, err := b.maxRequest(http.MethodPost, "/messages", query, map[string]string{"text": text}) return err } func (b *Bridge) GetMAXUpdates(marker *int64, timeout int) (*maxUpdatesResponse, error) { query := url.Values{ "limit": {"100"}, "types": {"message_created"}, } if timeout > 0 { query.Set("timeout", strconv.Itoa(timeout)) } if marker != nil { query.Set("marker", strconv.FormatInt(*marker, 10)) } data, err := b.maxRequest(http.MethodGet, "/updates", query, nil) if err != nil { return nil, err } var resp maxUpdatesResponse if err := json.Unmarshal(data, &resp); err != nil { return nil, err } return &resp, nil } func (b *Bridge) SubscribeMAXWebhook(webhookURL, secret string) error { body := map[string]any{ "url": webhookURL, "update_types": []string{"message_created"}, } if secret != "" { body["secret"] = secret } _, err := b.maxRequest(http.MethodPost, "/subscriptions", nil, body) return err } func maxUserDisplayName(u *maxUser) string { if u == nil { return "unknown" } name := u.FirstName if u.LastName != "" { if name != "" { name += " " } name += u.LastName } if name == "" && u.Username != "" { name = u.Username } if name == "" { return strconv.FormatInt(u.UserID, 10) } return name } func maxMessageChatID(msg *maxMessage) int64 { if msg == nil || msg.Recipient == nil { return 0 } return msg.Recipient.ChatID } func maxExtractMediaURLs(msg *maxMessage) []string { if msg == nil || msg.Body == nil { return nil } var urls []string for _, att := range msg.Body.Attachments { switch att.Type { case "image", "video", "audio", "file", "sticker": var payload maxAttachmentURL if err := json.Unmarshal(att.Payload, &payload); err == nil && payload.URL != "" { urls = append(urls, payload.URL) } } } return urls }