mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 03:26:37 +08:00
233 lines
6.4 KiB
Go
233 lines
6.4 KiB
Go
package service
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func telegramPostForm(ctx context.Context, cfg map[string]string, method string, form url.Values, timeout time.Duration) error {
|
|
apiURL, err := telegramMethodURL(cfg, cfg["bot_token"], method)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return telegramDoWithFallback(ctx, cfg, http.MethodPost, apiURL, form.Encode(), "application/x-www-form-urlencoded", timeout)
|
|
}
|
|
|
|
func telegramPostJSON(ctx context.Context, cfg map[string]string, method string, payload any, timeout time.Duration) error {
|
|
apiURL, err := telegramMethodURL(cfg, cfg["bot_token"], method)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return telegramDoWithFallback(ctx, cfg, http.MethodPost, apiURL, string(body), "application/json", timeout)
|
|
}
|
|
|
|
func telegramPostMultipart(ctx context.Context, cfg map[string]string, method string, fields map[string]string, fileField, fileName string, file []byte, timeout time.Duration) error {
|
|
apiURL, err := telegramMethodURL(cfg, cfg["bot_token"], method)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var body bytes.Buffer
|
|
writer := multipart.NewWriter(&body)
|
|
for key, value := range fields {
|
|
if strings.TrimSpace(value) == "" {
|
|
continue
|
|
}
|
|
if err := writer.WriteField(key, value); err != nil {
|
|
_ = writer.Close()
|
|
return err
|
|
}
|
|
}
|
|
part, err := writer.CreateFormFile(fileField, fileName)
|
|
if err != nil {
|
|
_ = writer.Close()
|
|
return err
|
|
}
|
|
if _, err := part.Write(file); err != nil {
|
|
_ = writer.Close()
|
|
return err
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
return err
|
|
}
|
|
return telegramDoWithFallback(ctx, cfg, http.MethodPost, apiURL, body.String(), writer.FormDataContentType(), timeout)
|
|
}
|
|
|
|
func telegramFetchRemotePhoto(ctx context.Context, cfg map[string]string, rawURL string, timeout time.Duration) ([]byte, string, error) {
|
|
rawURL = strings.TrimSpace(rawURL)
|
|
if rawURL == "" {
|
|
return nil, "", errors.New("telegram photo url required")
|
|
}
|
|
var lastErr error
|
|
for _, client := range telegramHTTPClients(timeout, cfg) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
req.Header.Set("User-Agent", "MediaStationGo/1.0")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
lastErr = sanitizeTelegramError(err)
|
|
continue
|
|
}
|
|
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024+1))
|
|
_ = resp.Body.Close()
|
|
if readErr != nil {
|
|
lastErr = readErr
|
|
continue
|
|
}
|
|
if resp.StatusCode >= 400 {
|
|
lastErr = fmt.Errorf("photo fetch error %d", resp.StatusCode)
|
|
continue
|
|
}
|
|
if len(body) == 0 {
|
|
lastErr = errors.New("photo fetch returned empty body")
|
|
continue
|
|
}
|
|
if len(body) > 10*1024*1024 {
|
|
lastErr = errors.New("photo too large")
|
|
continue
|
|
}
|
|
contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
|
|
return body, contentType, nil
|
|
}
|
|
if lastErr != nil {
|
|
return nil, "", lastErr
|
|
}
|
|
return nil, "", errors.New("photo fetch failed")
|
|
}
|
|
|
|
func deleteTelegramWebhook(ctx context.Context, cfg map[string]string) error {
|
|
payload := map[string]any{
|
|
"drop_pending_updates": false,
|
|
}
|
|
return telegramPostJSON(ctx, cfg, "deleteWebhook", payload, 15*time.Second)
|
|
}
|
|
|
|
func telegramDo(client *http.Client, req *http.Request) error {
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return sanitizeTelegramError(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode >= 400 {
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
return fmt.Errorf("telegram api error %d: %s", resp.StatusCode, sanitizeTelegramText(string(body)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func telegramDoWithFallback(ctx context.Context, cfg map[string]string, method, apiURL, body, contentType string, timeout time.Duration) error {
|
|
var lastErr error
|
|
for _, client := range telegramHTTPClients(timeout, cfg) {
|
|
req, err := http.NewRequestWithContext(ctx, method, apiURL, strings.NewReader(body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if contentType != "" {
|
|
req.Header.Set("Content-Type", contentType)
|
|
}
|
|
if err := telegramDo(client, req); err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
return nil
|
|
}
|
|
if lastErr != nil {
|
|
return lastErr
|
|
}
|
|
return errors.New("telegram request failed")
|
|
}
|
|
|
|
func telegramPostJSONDecode(ctx context.Context, cfg map[string]string, method string, payload any, timeout time.Duration, out any) error {
|
|
apiURL, err := telegramMethodURL(cfg, cfg["bot_token"], method)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var lastErr error
|
|
for _, client := range telegramHTTPClients(timeout, cfg) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, strings.NewReader(string(body)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
lastErr = sanitizeTelegramError(err)
|
|
continue
|
|
}
|
|
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
_ = resp.Body.Close()
|
|
if resp.StatusCode >= 400 {
|
|
lastErr = fmt.Errorf("telegram api error %d: %s", resp.StatusCode, sanitizeTelegramText(string(respBody)))
|
|
continue
|
|
}
|
|
if out != nil {
|
|
return json.Unmarshal(respBody, out)
|
|
}
|
|
return nil
|
|
}
|
|
if lastErr != nil {
|
|
return lastErr
|
|
}
|
|
return errors.New("telegram request failed")
|
|
}
|
|
|
|
func telegramGetJSONDecode(ctx context.Context, cfg map[string]string, method string, timeout time.Duration, out any) error {
|
|
apiURL, err := telegramMethodURL(cfg, cfg["bot_token"], method)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var lastErr error
|
|
for _, client := range telegramHTTPClients(timeout, cfg) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
lastErr = sanitizeTelegramError(err)
|
|
continue
|
|
}
|
|
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
_ = resp.Body.Close()
|
|
if resp.StatusCode >= 400 {
|
|
lastErr = fmt.Errorf("telegram api error %d: %s", resp.StatusCode, sanitizeTelegramText(string(respBody)))
|
|
continue
|
|
}
|
|
if out != nil {
|
|
return json.Unmarshal(respBody, out)
|
|
}
|
|
return nil
|
|
}
|
|
if lastErr != nil {
|
|
return lastErr
|
|
}
|
|
return errors.New("telegram request failed")
|
|
}
|
|
|
|
func telegramStringConfigFromAny(cfg map[string]any) map[string]string {
|
|
out := make(map[string]string, len(cfg))
|
|
for key, value := range cfg {
|
|
out[key] = str(value)
|
|
}
|
|
normalizeTelegramConfig(out)
|
|
return out
|
|
}
|