mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 03:56:38 +08:00
fix: stabilize telegram bot proxy and legacy permissions
This commit is contained in:
@@ -238,7 +238,7 @@ mkdir -p data cache media downloads
|
|||||||
```bash
|
```bash
|
||||||
cat > .env <<'EOF'
|
cat > .env <<'EOF'
|
||||||
# 固定版本;需要升级时改成新的 MediaStationGo-vX.Y.Z 后执行 docker compose pull && docker compose up -d
|
# 固定版本;需要升级时改成新的 MediaStationGo-vX.Y.Z 后执行 docker compose pull && docker compose up -d
|
||||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.25
|
||||||
MEDIASTATION_HTTP_PORT=18080
|
MEDIASTATION_HTTP_PORT=18080
|
||||||
|
|
||||||
# 程序数据和缓存建议放在 MediaStationGo 部署目录下,便于备份和迁移。
|
# 程序数据和缓存建议放在 MediaStationGo 部署目录下,便于备份和迁移。
|
||||||
@@ -307,7 +307,7 @@ vim docker-compose.yml
|
|||||||
#
|
#
|
||||||
# 镜像版本:
|
# 镜像版本:
|
||||||
# 默认拉取 latest;如需固定版本,创建 .env 并写入:
|
# 默认拉取 latest;如需固定版本,创建 .env 并写入:
|
||||||
# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.25
|
||||||
#
|
#
|
||||||
# 路径映射总览:
|
# 路径映射总览:
|
||||||
# /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。
|
# /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。
|
||||||
@@ -516,7 +516,7 @@ docker compose up -d
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat > .env <<'EOF'
|
cat > .env <<'EOF'
|
||||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.25
|
||||||
MEDIASTATION_HTTP_PORT=18080
|
MEDIASTATION_HTTP_PORT=18080
|
||||||
MEDIASTATION_DATA_DIR=./data
|
MEDIASTATION_DATA_DIR=./data
|
||||||
MEDIASTATION_CACHE_DIR=./cache
|
MEDIASTATION_CACHE_DIR=./cache
|
||||||
@@ -779,26 +779,26 @@ cd MediaStationGo
|
|||||||
|
|
||||||
| 平台 | 包名示例 |
|
| 平台 | 包名示例 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Linux x86_64 | `MediaStationGo-v0.0.24-linux-amd64.tar.gz` |
|
| Linux x86_64 | `MediaStationGo-v0.0.25-linux-amd64.tar.gz` |
|
||||||
| Linux ARM64 | `MediaStationGo-v0.0.24-linux-arm64.tar.gz` |
|
| Linux ARM64 | `MediaStationGo-v0.0.25-linux-arm64.tar.gz` |
|
||||||
| Windows x86_64 | `MediaStationGo-v0.0.24-windows-amd64.zip` |
|
| Windows x86_64 | `MediaStationGo-v0.0.25-windows-amd64.zip` |
|
||||||
| macOS Intel | `MediaStationGo-v0.0.24-darwin-amd64.tar.gz` |
|
| macOS Intel | `MediaStationGo-v0.0.25-darwin-amd64.tar.gz` |
|
||||||
| macOS Apple Silicon | `MediaStationGo-v0.0.24-darwin-arm64.tar.gz` |
|
| macOS Apple Silicon | `MediaStationGo-v0.0.25-darwin-arm64.tar.gz` |
|
||||||
|
|
||||||
部署步骤:
|
部署步骤:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Linux 示例
|
# Linux 示例
|
||||||
tar -xzf MediaStationGo-v0.0.24-linux-amd64.tar.gz
|
tar -xzf MediaStationGo-v0.0.25-linux-amd64.tar.gz
|
||||||
cd MediaStationGo-v0.0.24-linux-amd64
|
cd MediaStationGo-v0.0.25-linux-amd64
|
||||||
MEDIASTATION_APP_PORT=18080 ./mediastation-go
|
MEDIASTATION_APP_PORT=18080 ./mediastation-go
|
||||||
```
|
```
|
||||||
|
|
||||||
Windows:
|
Windows:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
Expand-Archive .\MediaStationGo-v0.0.24-windows-amd64.zip
|
Expand-Archive .\MediaStationGo-v0.0.25-windows-amd64.zip
|
||||||
cd .\MediaStationGo-v0.0.24-windows-amd64
|
cd .\MediaStationGo-v0.0.25-windows-amd64
|
||||||
$env:MEDIASTATION_APP_PORT = "18080"
|
$env:MEDIASTATION_APP_PORT = "18080"
|
||||||
.\mediastation-go.exe
|
.\mediastation-go.exe
|
||||||
```
|
```
|
||||||
|
|||||||
+11
-11
@@ -235,7 +235,7 @@ mkdir -p data cache media downloads
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat > .env <<'EOF'
|
cat > .env <<'EOF'
|
||||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.25
|
||||||
MEDIASTATION_HTTP_PORT=18080
|
MEDIASTATION_HTTP_PORT=18080
|
||||||
MEDIASTATION_DATA_DIR=./data
|
MEDIASTATION_DATA_DIR=./data
|
||||||
MEDIASTATION_CACHE_DIR=./cache
|
MEDIASTATION_CACHE_DIR=./cache
|
||||||
@@ -344,7 +344,7 @@ For production, pin a specific release tag instead of using `latest`. Recommende
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat > .env <<'EOF'
|
cat > .env <<'EOF'
|
||||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.25
|
||||||
MEDIASTATION_HTTP_PORT=18080
|
MEDIASTATION_HTTP_PORT=18080
|
||||||
MEDIASTATION_DATA_DIR=./data
|
MEDIASTATION_DATA_DIR=./data
|
||||||
MEDIASTATION_CACHE_DIR=./cache
|
MEDIASTATION_CACHE_DIR=./cache
|
||||||
@@ -593,25 +593,25 @@ Each release provides multi-platform archives:
|
|||||||
|
|
||||||
| Platform | Package example |
|
| Platform | Package example |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Linux x86_64 | `MediaStationGo-v0.0.24-linux-amd64.tar.gz` |
|
| Linux x86_64 | `MediaStationGo-v0.0.25-linux-amd64.tar.gz` |
|
||||||
| Linux ARM64 | `MediaStationGo-v0.0.24-linux-arm64.tar.gz` |
|
| Linux ARM64 | `MediaStationGo-v0.0.25-linux-arm64.tar.gz` |
|
||||||
| Windows x86_64 | `MediaStationGo-v0.0.24-windows-amd64.zip` |
|
| Windows x86_64 | `MediaStationGo-v0.0.25-windows-amd64.zip` |
|
||||||
| macOS Intel | `MediaStationGo-v0.0.24-darwin-amd64.tar.gz` |
|
| macOS Intel | `MediaStationGo-v0.0.25-darwin-amd64.tar.gz` |
|
||||||
| macOS Apple Silicon | `MediaStationGo-v0.0.24-darwin-arm64.tar.gz` |
|
| macOS Apple Silicon | `MediaStationGo-v0.0.25-darwin-arm64.tar.gz` |
|
||||||
|
|
||||||
Linux example:
|
Linux example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tar -xzf MediaStationGo-v0.0.24-linux-amd64.tar.gz
|
tar -xzf MediaStationGo-v0.0.25-linux-amd64.tar.gz
|
||||||
cd MediaStationGo-v0.0.24-linux-amd64
|
cd MediaStationGo-v0.0.25-linux-amd64
|
||||||
MEDIASTATION_APP_PORT=18080 ./mediastation-go
|
MEDIASTATION_APP_PORT=18080 ./mediastation-go
|
||||||
```
|
```
|
||||||
|
|
||||||
Windows example:
|
Windows example:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
Expand-Archive .\MediaStationGo-v0.0.24-windows-amd64.zip
|
Expand-Archive .\MediaStationGo-v0.0.25-windows-amd64.zip
|
||||||
cd .\MediaStationGo-v0.0.24-windows-amd64
|
cd .\MediaStationGo-v0.0.25-windows-amd64
|
||||||
$env:MEDIASTATION_APP_PORT = "18080"
|
$env:MEDIASTATION_APP_PORT = "18080"
|
||||||
.\mediastation-go.exe
|
.\mediastation-go.exe
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
#
|
#
|
||||||
# 镜像版本:
|
# 镜像版本:
|
||||||
# 默认拉取 latest;如需固定版本,创建 .env 并写入:
|
# 默认拉取 latest;如需固定版本,创建 .env 并写入:
|
||||||
# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.25
|
||||||
#
|
#
|
||||||
# 路径映射总览:
|
# 路径映射总览:
|
||||||
# /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。
|
# /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。
|
||||||
|
|||||||
@@ -346,11 +346,15 @@ func normalizeChannelInput(in *ChannelInput) {
|
|||||||
in.Config = map[string]any{}
|
in.Config = map[string]any{}
|
||||||
}
|
}
|
||||||
chatID := str(in.Config["chat_id"])
|
chatID := str(in.Config["chat_id"])
|
||||||
if chatID == "" || !strings.HasPrefix(chatID, "-") {
|
if chatID == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if str(in.Config["group_chat_id"]) == "" && str(in.Config["channel_chat_id"]) == "" && str(in.Config["command_chat_id"]) == "" {
|
if strings.HasPrefix(chatID, "-") && str(in.Config["group_chat_id"]) == "" && str(in.Config["channel_chat_id"]) == "" && str(in.Config["command_chat_id"]) == "" {
|
||||||
in.Config["group_chat_id"] = chatID
|
in.Config["group_chat_id"] = chatID
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(chatID, "-") && str(in.Config["admin_user_ids"]) == "" {
|
||||||
|
in.Config["admin_user_ids"] = chatID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,6 +373,8 @@ func telegramTargetChatIDs(cfg map[string]string) []string {
|
|||||||
chatID := strings.TrimSpace(cfg["chat_id"])
|
chatID := strings.TrimSpace(cfg["chat_id"])
|
||||||
if strings.HasPrefix(chatID, "-") {
|
if strings.HasPrefix(chatID, "-") {
|
||||||
targets = append(targets, chatID)
|
targets = append(targets, chatID)
|
||||||
|
} else if chatID != "" && strings.TrimSpace(cfg["admin_user_ids"]) == "" {
|
||||||
|
targets = append(targets, chatID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(targets) == 0 {
|
if len(targets) == 0 {
|
||||||
|
|||||||
@@ -46,17 +46,58 @@ func telegramMethodURL(cfg map[string]string, botToken, method string) (string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func telegramHTTPClient(timeout time.Duration, cfg map[string]string) *http.Client {
|
func telegramHTTPClient(timeout time.Duration, cfg map[string]string) *http.Client {
|
||||||
transport := NewExternalTransport()
|
clients := telegramHTTPClients(timeout, cfg)
|
||||||
proxyRaw := strings.TrimSpace(cfg["proxy_url"])
|
return clients[0]
|
||||||
if proxyRaw == "" {
|
}
|
||||||
proxyRaw = strings.TrimSpace(os.Getenv("MEDIASTATION_TELEGRAM_PROXY_URL"))
|
|
||||||
|
func telegramHTTPClients(timeout time.Duration, cfg map[string]string) []*http.Client {
|
||||||
|
clients := []*http.Client{}
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, proxyRaw := range telegramProxyCandidates(cfg) {
|
||||||
|
proxyURL, err := normalizeProxyURL(proxyRaw, "http")
|
||||||
|
if err != nil || proxyURL == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := proxyURL.String()
|
||||||
|
if seen[key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = true
|
||||||
|
transport := NewExternalTransport()
|
||||||
|
transport.Proxy = http.ProxyURL(proxyURL)
|
||||||
|
clients = append(clients, &http.Client{Timeout: timeout, Transport: transport})
|
||||||
}
|
}
|
||||||
if proxyRaw != "" {
|
transport := NewExternalTransport()
|
||||||
if proxyURL, err := normalizeProxyURL(proxyRaw, "http"); err == nil {
|
clients = append(clients, &http.Client{Timeout: timeout, Transport: transport})
|
||||||
transport.Proxy = http.ProxyURL(proxyURL)
|
return clients
|
||||||
|
}
|
||||||
|
|
||||||
|
func telegramProxyCandidates(cfg map[string]string) []string {
|
||||||
|
out := []string{}
|
||||||
|
for _, value := range []string{
|
||||||
|
cfg["proxy_url"],
|
||||||
|
os.Getenv("MEDIASTATION_TELEGRAM_PROXY_URL"),
|
||||||
|
} {
|
||||||
|
if strings.TrimSpace(value) != "" {
|
||||||
|
out = append(out, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &http.Client{Timeout: timeout, Transport: transport}
|
if len(out) > 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
for _, value := range []string{
|
||||||
|
"http://127.0.0.1:10808",
|
||||||
|
"http://127.0.0.1:10809",
|
||||||
|
"http://127.0.0.1:7890",
|
||||||
|
"http://127.0.0.1:7891",
|
||||||
|
"http://host.docker.internal:7890",
|
||||||
|
"http://host.docker.internal:10808",
|
||||||
|
"http://172.17.0.1:7890",
|
||||||
|
"http://172.17.0.1:10808",
|
||||||
|
} {
|
||||||
|
out = append(out, value)
|
||||||
|
}
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func telegramPostForm(ctx context.Context, cfg map[string]string, method string, form url.Values, timeout time.Duration) error {
|
func telegramPostForm(ctx context.Context, cfg map[string]string, method string, form url.Values, timeout time.Duration) error {
|
||||||
@@ -64,12 +105,7 @@ func telegramPostForm(ctx context.Context, cfg map[string]string, method string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, strings.NewReader(form.Encode()))
|
return telegramDoWithFallback(ctx, cfg, http.MethodPost, apiURL, form.Encode(), "application/x-www-form-urlencoded", timeout)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
return telegramDo(telegramHTTPClient(timeout, cfg), req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func telegramPostJSON(ctx context.Context, cfg map[string]string, method string, payload any, timeout time.Duration) error {
|
func telegramPostJSON(ctx context.Context, cfg map[string]string, method string, payload any, timeout time.Duration) error {
|
||||||
@@ -81,12 +117,7 @@ func telegramPostJSON(ctx context.Context, cfg map[string]string, method string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, strings.NewReader(string(body)))
|
return telegramDoWithFallback(ctx, cfg, http.MethodPost, apiURL, string(body), "application/json", timeout)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return telegramDo(telegramHTTPClient(timeout, cfg), req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func telegramDo(client *http.Client, req *http.Request) error {
|
func telegramDo(client *http.Client, req *http.Request) error {
|
||||||
@@ -102,11 +133,72 @@ func telegramDo(client *http.Client, req *http.Request) error {
|
|||||||
return nil
|
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 telegramStringConfigFromAny(cfg map[string]any) map[string]string {
|
func telegramStringConfigFromAny(cfg map[string]any) map[string]string {
|
||||||
out := make(map[string]string, len(cfg))
|
out := make(map[string]string, len(cfg))
|
||||||
for key, value := range cfg {
|
for key, value := range cfg {
|
||||||
out[key] = str(value)
|
out[key] = str(value)
|
||||||
}
|
}
|
||||||
|
normalizeTelegramConfig(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,3 +69,30 @@ func TestNormalizeTelegramChannelMigratesLegacyChatID(t *testing.T) {
|
|||||||
t.Fatalf("group_chat_id = %q, want -10001", got)
|
t.Fatalf("group_chat_id = %q, want -10001", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeTelegramChannelMigratesLegacyPrivateChatIDToAdmin(t *testing.T) {
|
||||||
|
cfg := map[string]string{"chat_id": "5812333517"}
|
||||||
|
normalizeTelegramConfig(cfg)
|
||||||
|
if got := cfg["admin_user_ids"]; got != "5812333517" {
|
||||||
|
t.Fatalf("admin_user_ids = %q, want legacy chat_id", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTelegramTargetChatIDsUsesLegacyPrivateChatID(t *testing.T) {
|
||||||
|
got := telegramTargetChatIDs(map[string]string{
|
||||||
|
"chat_id": "5812333517",
|
||||||
|
})
|
||||||
|
if len(got) != 1 || got[0] != "5812333517" {
|
||||||
|
t.Fatalf("got %#v, want legacy private chat target", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTelegramProxyCandidatesDefaultLocalFallbacks(t *testing.T) {
|
||||||
|
got := telegramProxyCandidates(map[string]string{})
|
||||||
|
joined := strings.Join(got, ",")
|
||||||
|
for _, want := range []string{"127.0.0.1:10808", "172.17.0.1:7890"} {
|
||||||
|
if !strings.Contains(joined, want) {
|
||||||
|
t.Fatalf("default proxy candidates %q missing %q", joined, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -501,7 +502,7 @@ func (s *TelegramBotService) pollLoop(ctx context.Context, cfg map[string]string
|
|||||||
s.log.Warn("telegram polling config invalid", zap.Error(err))
|
s.log.Warn("telegram polling config invalid", zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client := telegramHTTPClient(45*time.Second, cfg)
|
clients := telegramHTTPClients(45*time.Second, cfg)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -514,21 +515,12 @@ func (s *TelegramBotService) pollLoop(ctx context.Context, cfg map[string]string
|
|||||||
"offset": offset,
|
"offset": offset,
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
})
|
})
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, pollURL, strings.NewReader(string(reqBody)))
|
respBody, err := telegramPollingRequest(ctx, clients, pollURL, string(reqBody))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.log.Debug("telegram polling failed", zap.Error(err))
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Debug("telegram polling failed", zap.Error(sanitizeTelegramError(err)))
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
respBody, _ := io.ReadAll(resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
var result struct {
|
var result struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
@@ -554,6 +546,33 @@ func (s *TelegramBotService) pollLoop(ctx context.Context, cfg map[string]string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func telegramPollingRequest(ctx context.Context, clients []*http.Client, pollURL, body string) ([]byte, error) {
|
||||||
|
var lastErr error
|
||||||
|
for _, client := range clients {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, pollURL, strings.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = sanitizeTelegramError(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
respBody, _ := io.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
lastErr = fmt.Errorf("telegram api error %d: %s", resp.StatusCode, sanitizeTelegramText(string(respBody)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return respBody, nil
|
||||||
|
}
|
||||||
|
if lastErr != nil {
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
return nil, errors.New("telegram polling failed")
|
||||||
|
}
|
||||||
|
|
||||||
// ── Message Sending ──
|
// ── Message Sending ──
|
||||||
|
|
||||||
// reply 通过 Telegram Bot API 发送回复消息。
|
// reply 通过 Telegram Bot API 发送回复消息。
|
||||||
@@ -651,11 +670,14 @@ func (s *TelegramBotService) handleCallback(ctx context.Context, cb *TelegramCal
|
|||||||
if cb == nil || cb.Message == nil {
|
if cb == nil || cb.Message == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
channel := s.findChannelByChatID(ctx, cb.Message.Chat.ID)
|
msg := *cb.Message
|
||||||
|
msg.From = cb.From
|
||||||
|
channel := s.findChannelForMessage(ctx, &msg)
|
||||||
|
if channel == nil {
|
||||||
|
channel = s.findChannelByChatID(ctx, cb.Message.Chat.ID)
|
||||||
|
}
|
||||||
switch strings.TrimSpace(cb.Data) {
|
switch strings.TrimSpace(cb.Data) {
|
||||||
case "adult_toggle":
|
case "adult_toggle":
|
||||||
msg := *cb.Message
|
|
||||||
msg.From = cb.From
|
|
||||||
reply := s.cmdHideAdult(ctx, &msg, nil)
|
reply := s.cmdHideAdult(ctx, &msg, nil)
|
||||||
if reply.Text != "" {
|
if reply.Text != "" {
|
||||||
return s.reply(ctx, channel, cb.Message.Chat.ID, reply)
|
return s.reply(ctx, channel, cb.Message.Chat.ID, reply)
|
||||||
@@ -745,29 +767,17 @@ func (s *TelegramBotService) telegramUserIsChatMember(ctx context.Context, chann
|
|||||||
"chat_id": chatID,
|
"chat_id": chatID,
|
||||||
"user_id": telegramUserID,
|
"user_id": telegramUserID,
|
||||||
}
|
}
|
||||||
apiURL, err := telegramMethodURL(cfg, cfg["bot_token"], "getChatMember")
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
body, _ := json.Marshal(payload)
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, strings.NewReader(string(body)))
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
resp, err := telegramHTTPClient(8*time.Second, cfg).Do(req)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Warn("telegram getChatMember failed", zap.String("chat_id", chatID), zap.Int("telegram_user_id", telegramUserID), zap.Error(sanitizeTelegramError(err)))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
var result struct {
|
var result struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
Result struct {
|
Result struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
} `json:"result"`
|
} `json:"result"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil || !result.OK {
|
if err := telegramPostJSONDecode(ctx, cfg, "getChatMember", payload, 8*time.Second, &result); err != nil {
|
||||||
|
s.log.Warn("telegram getChatMember failed", zap.String("chat_id", chatID), zap.Int("telegram_user_id", telegramUserID), zap.Error(sanitizeTelegramError(err)))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !result.OK {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch strings.ToLower(result.Result.Status) {
|
switch strings.ToLower(result.Result.Status) {
|
||||||
@@ -784,13 +794,14 @@ func (s *TelegramBotService) telegramUserIDConfigured(channel *model.NotifyChann
|
|||||||
}
|
}
|
||||||
cfg := s.telegramChannelConfig(channel)
|
cfg := s.telegramChannelConfig(channel)
|
||||||
target := strconv.Itoa(telegramUserID)
|
target := strconv.Itoa(telegramUserID)
|
||||||
for _, value := range strings.FieldsFunc(cfg["admin_user_ids"], func(r rune) bool {
|
for _, value := range telegramConfiguredUserIDs(cfg["admin_user_ids"]) {
|
||||||
return r == ',' || r == ';' || r == ',' || r == ' ' || r == '\n' || r == '\t'
|
if value == target {
|
||||||
}) {
|
|
||||||
if strings.TrimSpace(value) == target {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(cfg["admin_user_ids"]) == "" && strings.TrimSpace(cfg["chat_id"]) == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -810,9 +821,29 @@ func telegramConfigFromChannel(crypto *CryptoService, channel *model.NotifyChann
|
|||||||
if err := json.Unmarshal([]byte(configStr), &cfg); err != nil || cfg == nil {
|
if err := json.Unmarshal([]byte(configStr), &cfg); err != nil || cfg == nil {
|
||||||
return map[string]string{}
|
return map[string]string{}
|
||||||
}
|
}
|
||||||
|
normalizeTelegramConfig(cfg)
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeTelegramConfig(cfg map[string]string) {
|
||||||
|
if cfg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chatID := strings.TrimSpace(cfg["chat_id"])
|
||||||
|
if chatID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(chatID, "-") {
|
||||||
|
if strings.TrimSpace(cfg["group_chat_id"]) == "" && strings.TrimSpace(cfg["channel_chat_id"]) == "" && strings.TrimSpace(cfg["command_chat_id"]) == "" {
|
||||||
|
cfg["group_chat_id"] = chatID
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(cfg["admin_user_ids"]) == "" {
|
||||||
|
cfg["admin_user_ids"] = chatID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TelegramBotService) upsertTelegramBinding(ctx context.Context, msg *TelegramMessage, userID string) error {
|
func (s *TelegramBotService) upsertTelegramBinding(ctx context.Context, msg *TelegramMessage, userID string) error {
|
||||||
name := strings.TrimSpace(msg.From.FirstName)
|
name := strings.TrimSpace(msg.From.FirstName)
|
||||||
if msg.From.Username != "" {
|
if msg.From.Username != "" {
|
||||||
|
|||||||
@@ -222,6 +222,9 @@ function normalizeInitialConfig(type: NotifyChannel['type'], raw: Record<string,
|
|||||||
if (type === 'telegram' && !base.group_chat_id && !base.channel_chat_id && base.chat_id?.startsWith('-')) {
|
if (type === 'telegram' && !base.group_chat_id && !base.channel_chat_id && base.chat_id?.startsWith('-')) {
|
||||||
base.group_chat_id = base.chat_id
|
base.group_chat_id = base.chat_id
|
||||||
}
|
}
|
||||||
|
if (type === 'telegram' && !base.admin_user_ids && base.chat_id && !base.chat_id.startsWith('-')) {
|
||||||
|
base.admin_user_ids = base.chat_id
|
||||||
|
}
|
||||||
delete base.chat_id
|
delete base.chat_id
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user