feat: enhance Safew chat handling and testing

- Updated `GroupsFromUpdates` function to return the number of updates processed, improving the polling mechanism.
- Added a new test `TestGroupsFromUpdatesCallbackAndJoinRequest` to validate handling of callback queries and join requests.
- Introduced `safewAllowedUpdates` to specify allowed update types in the polling request, enhancing chat management.
- Implemented `ensureWebhookCleared` method to manage webhook state before polling, improving reliability.
This commit is contained in:
2026-08-15 01:56:12 +08:00
parent 2000908bac
commit a07963e150
2 changed files with 94 additions and 27 deletions
+64 -26
View File
@@ -5,8 +5,10 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"strings"
"sync"
"time"
)
@@ -116,14 +118,27 @@ func (e *SafewAuthError) Error() string {
var safewLongPollClient = &http.Client{Timeout: 45 * time.Second}
var safewAllowedUpdates = []string{
"message",
"edited_message",
"my_chat_member",
"chat_member",
"chat_join_request",
"callback_query",
}
var safewWebhookCleared sync.Map
func (s *SafeWSender) PollGroupChats(token string, offset int64, timeout int) ([]SafewChat, int64, error) {
token = strings.TrimSpace(token)
if token == "" {
return nil, offset, fmt.Errorf("safew: token is required")
}
s.ensureWebhookCleared(token)
payload := map[string]any{
"timeout": timeout,
"limit": 100,
"timeout": timeout,
"limit": 100,
"allowed_updates": safewAllowedUpdates,
}
if offset > 0 {
payload["offset"] = offset
@@ -155,7 +170,7 @@ func (s *SafeWSender) PollGroupChats(token string, offset int64, timeout int) ([
}
return nil, offset, fmt.Errorf("safew: %s", api.Description)
}
chats, maxID, err := GroupsFromUpdates(respBody)
chats, maxID, n, err := GroupsFromUpdates(respBody)
if err != nil {
return nil, offset, err
}
@@ -163,9 +178,27 @@ func (s *SafeWSender) PollGroupChats(token string, offset int64, timeout int) ([
if maxID > 0 {
next = maxID + 1
}
slog.Info("safew getUpdates", "timeout", timeout, "updates", n, "groups", len(chats), "offset", next)
return chats, next, nil
}
func (s *SafeWSender) ensureWebhookCleared(token string) {
if _, loaded := safewWebhookCleared.LoadOrStore(token, true); loaded {
return
}
reqBody, _ := json.Marshal(map[string]any{"drop_pending_updates": false})
resp, err := http.Post(s.methodURL(token, "deleteWebhook"), "application/json", bytes.NewReader(reqBody))
if err != nil {
slog.Warn("safew deleteWebhook", "error", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
slog.Warn("safew deleteWebhook", "status", resp.StatusCode, "error", safewErrorDescription(body))
}
}
func parseSafewChatID(raw json.RawMessage) (string, error) {
raw = bytes.TrimSpace(raw)
if len(raw) == 0 || string(raw) == "null" {
@@ -220,7 +253,7 @@ type SafewChat struct {
Username *string `json:"username"`
}
func GroupsFromUpdates(body []byte) ([]SafewChat, int64, error) {
func GroupsFromUpdates(body []byte) ([]SafewChat, int64, int, error) {
dec := json.NewDecoder(bytes.NewReader(body))
dec.UseNumber()
var wrap struct {
@@ -228,14 +261,14 @@ func GroupsFromUpdates(body []byte) ([]SafewChat, int64, error) {
Result []json.RawMessage `json:"result"`
}
if err := dec.Decode(&wrap); err != nil {
return nil, 0, fmt.Errorf("safew updates decode: %w", err)
return nil, 0, 0, fmt.Errorf("safew updates decode: %w", err)
}
seen := map[string]SafewChat{}
var maxID int64
for _, item := range wrap.Result {
id, chat, err := parseUpdateItem(item)
if err != nil {
return nil, 0, err
return nil, 0, 0, err
}
if id > maxID {
maxID = id
@@ -252,7 +285,7 @@ func GroupsFromUpdates(body []byte) ([]SafewChat, int64, error) {
for _, c := range seen {
out = append(out, c)
}
return out, maxID, nil
return out, maxID, len(wrap.Result), nil
}
func parseUpdateItem(item json.RawMessage) (int64, *SafewChat, error) {
@@ -269,7 +302,10 @@ func parseUpdateItem(item json.RawMessage) (int64, *SafewChat, error) {
updateID, _ = n.Int64()
}
}
for _, key := range []string{"message", "edited_message", "channel_post", "edited_channel_post", "my_chat_member", "chat_member"} {
for _, key := range []string{
"message", "edited_message", "channel_post", "edited_channel_post",
"my_chat_member", "chat_member", "chat_join_request", "callback_query",
} {
raw, ok := u[key]
if !ok {
continue
@@ -289,26 +325,28 @@ func chatFromNested(raw json.RawMessage) *SafewChat {
if err := dec.Decode(&obj); err != nil {
return nil
}
chatRaw, ok := obj["chat"]
if !ok {
return nil
if chatRaw, ok := obj["chat"]; ok {
dec = json.NewDecoder(bytes.NewReader(chatRaw))
dec.UseNumber()
var c struct {
ID json.Number `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Username *string `json:"username"`
}
if err := dec.Decode(&c); err != nil {
return nil
}
id := strings.TrimSpace(c.ID.String())
if id == "" {
return nil
}
return &SafewChat{ID: id, Type: c.Type, Title: c.Title, Username: c.Username}
}
dec = json.NewDecoder(bytes.NewReader(chatRaw))
dec.UseNumber()
var c struct {
ID json.Number `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Username *string `json:"username"`
if msgRaw, ok := obj["message"]; ok {
return chatFromNested(msgRaw)
}
if err := dec.Decode(&c); err != nil {
return nil
}
id := strings.TrimSpace(c.ID.String())
if id == "" {
return nil
}
return &SafewChat{ID: id, Type: c.Type, Title: c.Title, Username: c.Username}
return nil
}
func FilterSafewChats(chats []SafewChat, q string) []SafewChat {