refactor: modernize codebase for Go 1.26 and fix adapter issues

- Upgrade Go version from 1.24 to 1.26.1 with updated dependencies
- Replace interface{} with any type throughout codebase
- Add message deduplication in OneBot11 adapter to prevent duplicate event processing
- URL-encode access token in WebSocket connections to handle special characters
- Remove duplicate bot startup hooks in DI providers (now handled by botManager.StartAll)
- Use slices.Clone and errgroup.Go patterns for cleaner concurrent code
- Apply omitzero JSON tags for optional fields (Go 1.24+ feature)
This commit is contained in:
lafay
2026-03-18 01:22:49 +08:00
parent f3a72264af
commit f3220a2381
32 changed files with 2262 additions and 296 deletions

View File

@@ -225,7 +225,7 @@ func (a *Adapter) handleEvents(eventChan <-chan []byte) {
}
// SendAction 发送动作
func (a *Adapter) SendAction(ctx context.Context, action protocol.Action) (map[string]interface{}, error) {
func (a *Adapter) SendAction(ctx context.Context, action protocol.Action) (map[string]any, error) {
// 调用 API
resp, err := a.apiClient.Call(ctx, string(action.GetType()), action.GetParams())
if err != nil {
@@ -303,8 +303,8 @@ func (a *Adapter) IsConnected() bool {
}
// GetStats 获取统计信息
func (a *Adapter) GetStats() map[string]interface{} {
stats := map[string]interface{}{
func (a *Adapter) GetStats() map[string]any {
stats := map[string]any{
"protocol": "milky",
"self_id": a.selfID,
"event_mode": a.config.EventMode,
@@ -320,7 +320,7 @@ func (a *Adapter) GetStats() map[string]interface{} {
}
// CallAPI 直接调用 API提供给 Bot 使用)
func (a *Adapter) CallAPI(ctx context.Context, endpoint string, params map[string]interface{}) (*APIResponse, error) {
func (a *Adapter) CallAPI(ctx context.Context, endpoint string, params map[string]any) (*APIResponse, error) {
return a.apiClient.Call(ctx, endpoint, params)
}

View File

@@ -47,7 +47,7 @@ func NewAPIClient(baseURL, accessToken string, timeout time.Duration, retryCount
// endpoint: API 端点名称(如 "send_private_message"
// input: 输入参数(会被序列化为 JSON
// 返回: 响应数据和错误
func (c *APIClient) Call(ctx context.Context, endpoint string, input interface{}) (*APIResponse, error) {
func (c *APIClient) Call(ctx context.Context, endpoint string, input any) (*APIResponse, error) {
// 序列化输入参数
var inputData []byte
var err error
@@ -126,7 +126,7 @@ func (c *APIClient) doRequest(ctx context.Context, url string, inputData []byte)
// CallWithoutRetry 调用 API不重试
// 注意pkg/net.HTTPClient 的通用请求已经内置了重试机制
// 这个方法保留是为了向后兼容,但仍然会使用底层的重试机制
func (c *APIClient) CallWithoutRetry(ctx context.Context, endpoint string, input interface{}) (*APIResponse, error) {
func (c *APIClient) CallWithoutRetry(ctx context.Context, endpoint string, input any) (*APIResponse, error) {
return c.Call(ctx, endpoint, input)
}

View File

@@ -105,7 +105,7 @@ func (b *Bot) Disconnect(ctx context.Context) error {
}
// SendAction 发送动作
func (b *Bot) SendAction(ctx context.Context, action protocol.Action) (map[string]interface{}, error) {
func (b *Bot) SendAction(ctx context.Context, action protocol.Action) (map[string]any, error) {
if b.status != protocol.BotStatusRunning {
return nil, fmt.Errorf("bot is not running")
}
@@ -119,8 +119,8 @@ func (b *Bot) GetAdapter() *Adapter {
}
// GetInfo 获取机器人信息
func (b *Bot) GetInfo() map[string]interface{} {
return map[string]interface{}{
func (b *Bot) GetInfo() map[string]any {
return map[string]any{
"id": b.id,
"protocol": "milky",
"status": b.status,
@@ -144,7 +144,7 @@ func (b *Bot) SetStatus(status protocol.BotStatus) {
// SendPrivateMessage 发送私聊消息
func (b *Bot) SendPrivateMessage(ctx context.Context, userID int64, segments []OutgoingSegment) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"user_id": userID,
"segments": segments,
}
@@ -153,7 +153,7 @@ func (b *Bot) SendPrivateMessage(ctx context.Context, userID int64, segments []O
// SendGroupMessage 发送群消息
func (b *Bot) SendGroupMessage(ctx context.Context, groupID int64, segments []OutgoingSegment) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"segments": segments,
}
@@ -162,7 +162,7 @@ func (b *Bot) SendGroupMessage(ctx context.Context, groupID int64, segments []Ou
// SendTempMessage 发送临时消息
func (b *Bot) SendTempMessage(ctx context.Context, groupID, userID int64, segments []OutgoingSegment) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"user_id": userID,
"segments": segments,
@@ -172,7 +172,7 @@ func (b *Bot) SendTempMessage(ctx context.Context, groupID, userID int64, segmen
// RecallMessage 撤回消息
func (b *Bot) RecallMessage(ctx context.Context, messageScene string, peerID, messageSeq int64) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"message_scene": messageScene,
"peer_id": peerID,
"message_seq": messageSeq,
@@ -192,7 +192,7 @@ func (b *Bot) GetGroupList(ctx context.Context) (*APIResponse, error) {
// GetGroupMemberList 获取群成员列表
func (b *Bot) GetGroupMemberList(ctx context.Context, groupID int64) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
}
return b.adapter.CallAPI(ctx, "get_group_member_list", params)
@@ -200,7 +200,7 @@ func (b *Bot) GetGroupMemberList(ctx context.Context, groupID int64) (*APIRespon
// GetGroupMemberInfo 获取群成员信息
func (b *Bot) GetGroupMemberInfo(ctx context.Context, groupID, userID int64) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"user_id": userID,
}
@@ -209,7 +209,7 @@ func (b *Bot) GetGroupMemberInfo(ctx context.Context, groupID, userID int64) (*A
// SetGroupAdmin 设置群管理员
func (b *Bot) SetGroupAdmin(ctx context.Context, groupID, userID int64, isSet bool) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"user_id": userID,
"is_set": isSet,
@@ -219,7 +219,7 @@ func (b *Bot) SetGroupAdmin(ctx context.Context, groupID, userID int64, isSet bo
// SetGroupCard 设置群名片
func (b *Bot) SetGroupCard(ctx context.Context, groupID, userID int64, card string) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"user_id": userID,
"card": card,
@@ -229,7 +229,7 @@ func (b *Bot) SetGroupCard(ctx context.Context, groupID, userID int64, card stri
// SetGroupName 设置群名
func (b *Bot) SetGroupName(ctx context.Context, groupID int64, groupName string) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"group_name": groupName,
}
@@ -238,7 +238,7 @@ func (b *Bot) SetGroupName(ctx context.Context, groupID int64, groupName string)
// KickGroupMember 踢出群成员
func (b *Bot) KickGroupMember(ctx context.Context, groupID, userID int64, rejectAddRequest bool) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"user_id": userID,
"reject_add_request": rejectAddRequest,
@@ -248,7 +248,7 @@ func (b *Bot) KickGroupMember(ctx context.Context, groupID, userID int64, reject
// MuteGroupMember 禁言群成员
func (b *Bot) MuteGroupMember(ctx context.Context, groupID, userID int64, duration int32) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"user_id": userID,
"duration": duration,
@@ -258,7 +258,7 @@ func (b *Bot) MuteGroupMember(ctx context.Context, groupID, userID int64, durati
// MuteGroupWhole 全体禁言
func (b *Bot) MuteGroupWhole(ctx context.Context, groupID int64, isMute bool) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"is_mute": isMute,
}
@@ -267,7 +267,7 @@ func (b *Bot) MuteGroupWhole(ctx context.Context, groupID int64, isMute bool) (*
// LeaveGroup 退出群
func (b *Bot) LeaveGroup(ctx context.Context, groupID int64) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
}
return b.adapter.CallAPI(ctx, "leave_group", params)
@@ -275,7 +275,7 @@ func (b *Bot) LeaveGroup(ctx context.Context, groupID int64) (*APIResponse, erro
// HandleFriendRequest 处理好友请求
func (b *Bot) HandleFriendRequest(ctx context.Context, initiatorUID string, accept bool) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"initiator_uid": initiatorUID,
"accept": accept,
}
@@ -284,7 +284,7 @@ func (b *Bot) HandleFriendRequest(ctx context.Context, initiatorUID string, acce
// HandleGroupJoinRequest 处理入群申请
func (b *Bot) HandleGroupJoinRequest(ctx context.Context, groupID, notificationSeq int64, accept bool, rejectReason string) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"notification_seq": notificationSeq,
"accept": accept,
@@ -295,7 +295,7 @@ func (b *Bot) HandleGroupJoinRequest(ctx context.Context, groupID, notificationS
// HandleGroupInvitation 处理群邀请
func (b *Bot) HandleGroupInvitation(ctx context.Context, groupID, invitationSeq int64, accept bool) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"group_id": groupID,
"invitation_seq": invitationSeq,
"accept": accept,
@@ -305,7 +305,7 @@ func (b *Bot) HandleGroupInvitation(ctx context.Context, groupID, invitationSeq
// UploadFile 上传文件
func (b *Bot) UploadFile(ctx context.Context, fileType, filePath string) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"file_type": fileType,
"file_path": filePath,
}
@@ -314,7 +314,7 @@ func (b *Bot) UploadFile(ctx context.Context, fileType, filePath string) (*APIRe
// GetFile 获取文件
func (b *Bot) GetFile(ctx context.Context, fileID string) (*APIResponse, error) {
params := map[string]interface{}{
params := map[string]any{
"file_id": fileID,
}
return b.adapter.CallAPI(ctx, "get_file", params)

View File

@@ -101,7 +101,7 @@ func (c *EventConverter) convertMessageEvent(milkyEvent *Event) (protocol.Event,
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}{
Data: map[string]any{
"peer_id": msgData.PeerID,
"message_seq": msgData.MessageSeq,
"sender_id": msgData.SenderID,
@@ -145,7 +145,7 @@ func (c *EventConverter) convertFriendRequestEvent(milkyEvent *Event) (protocol.
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}{
Data: map[string]any{
"initiator_id": data.InitiatorID,
"initiator_uid": data.InitiatorUID,
"comment": data.Comment,
@@ -178,7 +178,7 @@ func (c *EventConverter) convertGroupJoinRequestEvent(milkyEvent *Event) (protoc
SubType: "add",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}{
Data: map[string]any{
"group_id": data.GroupID,
"notification_seq": data.NotificationSeq,
"is_filtered": data.IsFiltered,
@@ -212,7 +212,7 @@ func (c *EventConverter) convertGroupInvitedJoinRequestEvent(milkyEvent *Event)
SubType: "invite",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}{
Data: map[string]any{
"group_id": data.GroupID,
"notification_seq": data.NotificationSeq,
"initiator_id": data.InitiatorID,
@@ -245,7 +245,7 @@ func (c *EventConverter) convertGroupInvitationEvent(milkyEvent *Event) (protoco
SubType: "invite_self",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}{
Data: map[string]any{
"group_id": data.GroupID,
"invitation_seq": data.InvitationSeq,
"initiator_id": data.InitiatorID,
@@ -277,7 +277,7 @@ func (c *EventConverter) convertMessageRecallEvent(milkyEvent *Event) (protocol.
SubType: data.MessageScene,
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}{
Data: map[string]any{
"message_scene": data.MessageScene,
"peer_id": data.PeerID,
"message_seq": data.MessageSeq,
@@ -310,7 +310,7 @@ func (c *EventConverter) convertBotOfflineEvent(milkyEvent *Event) (protocol.Eve
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}{
Data: map[string]any{
"reason": data.Reason,
},
},
@@ -337,7 +337,7 @@ func (c *EventConverter) convertFriendNudgeEvent(milkyEvent *Event) (protocol.Ev
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
UserID: strconv.FormatInt(data.UserID, 10),
}
@@ -362,7 +362,7 @@ func (c *EventConverter) convertFriendFileUploadEvent(milkyEvent *Event) (protoc
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
UserID: strconv.FormatInt(data.UserID, 10),
}
@@ -392,7 +392,7 @@ func (c *EventConverter) convertGroupAdminChangeEvent(milkyEvent *Event) (protoc
SubType: subType,
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
UserID: strconv.FormatInt(data.UserID, 10),
@@ -423,7 +423,7 @@ func (c *EventConverter) convertGroupEssenceMessageChangeEvent(milkyEvent *Event
SubType: subType,
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
}
@@ -448,7 +448,7 @@ func (c *EventConverter) convertGroupMemberIncreaseEvent(milkyEvent *Event) (pro
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
UserID: strconv.FormatInt(data.UserID, 10),
@@ -478,7 +478,7 @@ func (c *EventConverter) convertGroupMemberDecreaseEvent(milkyEvent *Event) (pro
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
UserID: strconv.FormatInt(data.UserID, 10),
@@ -508,7 +508,7 @@ func (c *EventConverter) convertGroupNameChangeEvent(milkyEvent *Event) (protoco
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
Operator: strconv.FormatInt(data.OperatorID, 10),
@@ -539,7 +539,7 @@ func (c *EventConverter) convertGroupMessageReactionEvent(milkyEvent *Event) (pr
SubType: subType,
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
UserID: strconv.FormatInt(data.UserID, 10),
@@ -570,7 +570,7 @@ func (c *EventConverter) convertGroupMuteEvent(milkyEvent *Event) (protocol.Even
SubType: subType,
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
UserID: strconv.FormatInt(data.UserID, 10),
@@ -602,7 +602,7 @@ func (c *EventConverter) convertGroupWholeMuteEvent(milkyEvent *Event) (protocol
SubType: subType,
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
Operator: strconv.FormatInt(data.OperatorID, 10),
@@ -628,7 +628,7 @@ func (c *EventConverter) convertGroupNudgeEvent(milkyEvent *Event) (protocol.Eve
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
UserID: strconv.FormatInt(data.SenderID, 10),
@@ -654,7 +654,7 @@ func (c *EventConverter) convertGroupFileUploadEvent(milkyEvent *Event) (protoco
SubType: "",
Timestamp: milkyEvent.Time,
SelfID: selfID,
Data: map[string]interface{}(milkyEvent.Data),
Data: map[string]any(milkyEvent.Data),
},
GroupID: strconv.FormatInt(data.GroupID, 10),
UserID: strconv.FormatInt(data.UserID, 10),

View File

@@ -25,14 +25,14 @@ type Boolean = bool
// IncomingSegment 接收消息段(联合类型)
type IncomingSegment struct {
Type string `json:"type"`
Data map[string]interface{} `json:"data"`
Type string `json:"type"`
Data map[string]any `json:"data"`
}
// OutgoingSegment 发送消息段(联合类型)
type OutgoingSegment struct {
Type string `json:"type"`
Data map[string]interface{} `json:"data"`
Type string `json:"type"`
Data map[string]any `json:"data"`
}
// IncomingForwardedMessage 接收转发消息
@@ -318,10 +318,10 @@ type GroupFileUploadEventData struct {
// Event Milky 事件(联合类型,使用 event_type 区分)
type Event struct {
EventType string `json:"event_type"`
Time int64 `json:"time"`
SelfID int64 `json:"self_id"`
Data map[string]interface{} `json:"data"`
EventType string `json:"event_type"`
Time int64 `json:"time"`
SelfID int64 `json:"self_id"`
Data map[string]any `json:"data"`
}
// 事件类型常量
@@ -353,10 +353,10 @@ const (
// APIResponse API 响应
type APIResponse struct {
Status string `json:"status"` // ok, failed
RetCode int `json:"retcode"`
Data map[string]interface{} `json:"data,omitempty"`
Message string `json:"message,omitempty"`
Status string `json:"status"` // ok, failed
RetCode int `json:"retcode"`
Data map[string]any `json:"data,omitempty"`
Message string `json:"message,omitempty"`
}
// 响应状态码

View File

@@ -82,16 +82,16 @@ func ConvertAction(action protocol.Action) string {
// SendPrivateMessageAction 发送私聊消息动作
type SendPrivateMessageAction struct {
UserID int64 `json:"user_id"`
Message interface{} `json:"message"`
AutoEscape bool `json:"auto_escape,omitempty"`
UserID int64 `json:"user_id"`
Message any `json:"message"`
AutoEscape bool `json:"auto_escape,omitempty"`
}
// SendGroupMessageAction 发送群消息动作
type SendGroupMessageAction struct {
GroupID int64 `json:"group_id"`
Message interface{} `json:"message"`
AutoEscape bool `json:"auto_escape,omitempty"`
GroupID int64 `json:"group_id"`
Message any `json:"message"`
AutoEscape bool `json:"auto_escape,omitempty"`
}
// DeleteMessageAction 撤回消息动作
@@ -224,12 +224,12 @@ type SetRestartAction struct {
// ActionResponse API响应
type ActionResponse struct {
Status string `json:"status"`
RetCode int `json:"retcode"`
Data map[string]interface{} `json:"data,omitempty"`
Echo string `json:"echo,omitempty"`
Message string `json:"message,omitempty"`
Wording string `json:"wording,omitempty"`
Status string `json:"status"`
RetCode int `json:"retcode"`
Data map[string]any `json:"data,omitempty"`
Echo string `json:"echo,omitempty"`
Message string `json:"message,omitempty"`
Wording string `json:"wording,omitempty"`
}
// 响应状态码常量
@@ -245,7 +245,7 @@ const (
)
// BuildActionRequest 构建动作请求
func BuildActionRequest(action string, params map[string]interface{}, echo string) *OB11Action {
func BuildActionRequest(action string, params map[string]any, echo string) *OB11Action {
return &OB11Action{
Action: action,
Params: params,
@@ -254,8 +254,8 @@ func BuildActionRequest(action string, params map[string]interface{}, echo strin
}
// BuildSendPrivateMsg 构建发送私聊消息请求
func BuildSendPrivateMsg(userID int64, message interface{}, autoEscape bool) map[string]interface{} {
return map[string]interface{}{
func BuildSendPrivateMsg(userID int64, message any, autoEscape bool) map[string]any {
return map[string]any{
"user_id": userID,
"message": message,
"auto_escape": autoEscape,
@@ -263,8 +263,8 @@ func BuildSendPrivateMsg(userID int64, message interface{}, autoEscape bool) map
}
// BuildSendGroupMsg 构建发送群消息请求
func BuildSendGroupMsg(groupID int64, message interface{}, autoEscape bool) map[string]interface{} {
return map[string]interface{}{
func BuildSendGroupMsg(groupID int64, message any, autoEscape bool) map[string]any {
return map[string]any{
"group_id": groupID,
"message": message,
"auto_escape": autoEscape,
@@ -272,15 +272,15 @@ func BuildSendGroupMsg(groupID int64, message interface{}, autoEscape bool) map[
}
// BuildDeleteMsg 构建撤回消息请求
func BuildDeleteMsg(messageID int32) map[string]interface{} {
return map[string]interface{}{
func BuildDeleteMsg(messageID int32) map[string]any {
return map[string]any{
"message_id": messageID,
}
}
// BuildSetGroupBan 构建群组禁言请求
func BuildSetGroupBan(groupID, userID int64, duration int64) map[string]interface{} {
return map[string]interface{}{
func BuildSetGroupBan(groupID, userID int64, duration int64) map[string]any {
return map[string]any{
"group_id": groupID,
"user_id": userID,
"duration": duration,
@@ -288,8 +288,8 @@ func BuildSetGroupBan(groupID, userID int64, duration int64) map[string]interfac
}
// BuildSetGroupKick 构建群组踢人请求
func BuildSetGroupKick(groupID, userID int64, rejectAddRequest bool) map[string]interface{} {
return map[string]interface{}{
func BuildSetGroupKick(groupID, userID int64, rejectAddRequest bool) map[string]any {
return map[string]any{
"group_id": groupID,
"user_id": userID,
"reject_add_request": rejectAddRequest,
@@ -297,8 +297,8 @@ func BuildSetGroupKick(groupID, userID int64, rejectAddRequest bool) map[string]
}
// BuildSetGroupCard 构建设置群名片请求
func BuildSetGroupCard(groupID, userID int64, card string) map[string]interface{} {
return map[string]interface{}{
func BuildSetGroupCard(groupID, userID int64, card string) map[string]any {
return map[string]any{
"group_id": groupID,
"user_id": userID,
"card": card,

View File

@@ -3,6 +3,7 @@ package onebot11
import (
"context"
"fmt"
"net/url"
"sync"
"time"
@@ -28,6 +29,10 @@ type Adapter struct {
wsConnection *net.WebSocketConnection
ctx context.Context
cancel context.CancelFunc
// 消息去重
processedMessages map[int64]time.Time
messageMu sync.RWMutex
}
// Config OneBot11配置
@@ -65,16 +70,20 @@ func NewAdapter(config *Config, logger *zap.Logger, wsManager *net.WebSocketMana
}
adapter := &Adapter{
config: config,
logger: logger.Named("onebot11"),
wsManager: wsManager,
wsWaiter: NewWSResponseWaiter(timeout, logger),
eventBus: eventBus,
selfID: config.SelfID,
ctx: ctx,
cancel: cancel,
config: config,
logger: logger.Named("onebot11"),
wsManager: wsManager,
wsWaiter: NewWSResponseWaiter(timeout, logger),
eventBus: eventBus,
selfID: config.SelfID,
ctx: ctx,
cancel: cancel,
processedMessages: make(map[int64]time.Time),
}
// 启动定期清理过期消息的 goroutine
go adapter.cleanupProcessedMessages()
// 如果使用HTTP连接初始化HTTP客户端
if config.ConnectionType == "http" && config.HTTPUrl != "" {
adapter.httpClient = NewHTTPClient(config.HTTPUrl, config.AccessToken, timeout, logger)
@@ -83,6 +92,43 @@ func NewAdapter(config *Config, logger *zap.Logger, wsManager *net.WebSocketMana
return adapter
}
// cleanupProcessedMessages 定期清理过期的已处理消息记录
func (a *Adapter) cleanupProcessedMessages() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-a.ctx.Done():
return
case <-ticker.C:
a.messageMu.Lock()
now := time.Now()
for msgID, processedAt := range a.processedMessages {
if now.Sub(processedAt) > 10*time.Minute {
delete(a.processedMessages, msgID)
}
}
a.messageMu.Unlock()
}
}
}
// isMessageProcessed 检查消息是否已处理
func (a *Adapter) isMessageProcessed(messageID int64) bool {
a.messageMu.RLock()
_, exists := a.processedMessages[messageID]
a.messageMu.RUnlock()
return exists
}
// markMessageProcessed 标记消息为已处理
func (a *Adapter) markMessageProcessed(messageID int64) {
a.messageMu.Lock()
a.processedMessages[messageID] = time.Now()
a.messageMu.Unlock()
}
// Name 获取协议名称
func (a *Adapter) Name() string {
return "OneBot"
@@ -174,7 +220,7 @@ func (a *Adapter) GetSelfID() string {
}
// SendAction 发送动作
func (a *Adapter) SendAction(ctx context.Context, action protocol.Action) (map[string]interface{}, error) {
func (a *Adapter) SendAction(ctx context.Context, action protocol.Action) (map[string]any, error) {
// 序列化为OneBot11格式
data, err := a.SerializeAction(action)
if err != nil {
@@ -228,7 +274,7 @@ func (a *Adapter) SerializeAction(action protocol.Action) ([]byte, error) {
}
// 复制参数并转换消息链
params := make(map[string]interface{})
params := make(map[string]any)
for k, v := range action.GetParams() {
// 检查是否是消息链
if k == "message" {
@@ -259,10 +305,10 @@ func (a *Adapter) connectWebSocket(ctx context.Context) error {
zap.String("url", a.config.WSUrl),
zap.Bool("has_token", a.config.AccessToken != ""))
// 添加访问令牌到URL
// 添加访问令牌到URL需要对token进行URL编码以处理特殊字符
wsURL := a.config.WSUrl
if a.config.AccessToken != "" {
wsURL += "?access_token=" + a.config.AccessToken
wsURL += "?access_token=" + url.QueryEscape(a.config.AccessToken)
a.logger.Debug("Added access token to WebSocket URL")
}
@@ -342,7 +388,7 @@ func (a *Adapter) connectHTTPPost(ctx context.Context) error {
}
// sendActionWebSocket 通过WebSocket发送动作
func (a *Adapter) sendActionWebSocket(data []byte) (map[string]interface{}, error) {
func (a *Adapter) sendActionWebSocket(data []byte) (map[string]any, error) {
if a.wsConnection == nil {
return nil, fmt.Errorf("websocket connection not established")
}
@@ -383,7 +429,7 @@ func (a *Adapter) sendActionWebSocket(data []byte) (map[string]interface{}, erro
}
// sendActionHTTP 通过HTTP发送动作
func (a *Adapter) sendActionHTTP(data []byte) (map[string]interface{}, error) {
func (a *Adapter) sendActionHTTP(data []byte) (map[string]any, error) {
if a.httpClient == nil {
return nil, fmt.Errorf("http client not initialized")
}
@@ -439,7 +485,7 @@ func (a *Adapter) handleWebSocketMessages() {
zap.String("preview", string(message[:min(len(message), 200)])))
// 尝试解析为响应(先检查是否有 echo 字段)
var tempMap map[string]interface{}
var tempMap map[string]any
if err := sonic.Unmarshal(message, &tempMap); err == nil {
if echo, ok := tempMap["echo"].(string); ok && echo != "" {
// 有 echo 字段说明是API响应
@@ -478,6 +524,24 @@ func (a *Adapter) handleWebSocketMessages() {
continue
}
// 消息去重检查(基于 message_id
data := event.GetData()
// message_id 可能是 int32 或 int64
var messageID int64
if mid, ok := data["message_id"].(int32); ok && mid > 0 {
messageID = int64(mid)
} else if mid, ok := data["message_id"].(int64); ok && mid > 0 {
messageID = mid
}
if messageID > 0 {
if a.isMessageProcessed(messageID) {
a.logger.Warn("Duplicate message detected, skipping",
zap.Int64("message_id", messageID))
continue
}
a.markMessageProcessed(messageID)
}
// 发布事件到事件总线
a.logger.Info("Publishing event to event bus",
zap.String("event_type", string(event.GetType())),

View File

@@ -41,9 +41,9 @@ func NewHTTPClient(baseURL, accessToken string, timeout time.Duration, logger *z
}
// Call 调用API
func (c *HTTPClient) Call(ctx context.Context, action string, params map[string]interface{}) (*OB11Response, error) {
func (c *HTTPClient) Call(ctx context.Context, action string, params map[string]any) (*OB11Response, error) {
// 构建请求数据
reqData := map[string]interface{}{
reqData := map[string]any{
"action": action,
"params": params,
}

View File

@@ -14,7 +14,7 @@ func (a *Adapter) convertToEvent(raw *RawEvent) (protocol.Event, error) {
baseEvent := &protocol.BaseEvent{
Timestamp: raw.Time,
SelfID: strconv.FormatInt(raw.SelfID, 10),
Data: make(map[string]interface{}),
Data: make(map[string]any),
}
switch raw.PostType {
@@ -49,7 +49,7 @@ func (a *Adapter) convertMessageEvent(raw *RawEvent, base *protocol.BaseEvent) (
}
if raw.Sender != nil {
senderData := map[string]interface{}{
senderData := map[string]any{
"user_id": raw.Sender.UserID,
"nickname": raw.Sender.Nickname,
}
@@ -69,7 +69,7 @@ func (a *Adapter) convertMessageEvent(raw *RawEvent, base *protocol.BaseEvent) (
}
if raw.Anonymous != nil {
base.Data["anonymous"] = map[string]interface{}{
base.Data["anonymous"] = map[string]any{
"id": raw.Anonymous.ID,
"name": raw.Anonymous.Name,
"flag": raw.Anonymous.Flag,
@@ -111,7 +111,7 @@ func (a *Adapter) convertNoticeEvent(raw *RawEvent, base *protocol.BaseEvent) (p
case NoticeTypeGroupUpload:
// 文件上传信息
if raw.File != nil {
base.Data["file"] = map[string]interface{}{
base.Data["file"] = map[string]any{
"id": raw.File.ID,
"name": raw.File.Name,
"size": raw.File.Size,
@@ -156,12 +156,12 @@ func (a *Adapter) convertMetaEvent(raw *RawEvent, base *protocol.BaseEvent) (pro
base.DetailType = raw.MetaType
if raw.Status != nil {
statusData := map[string]interface{}{
statusData := map[string]any{
"online": raw.Status.Online,
"good": raw.Status.Good,
}
if raw.Status.Stat != nil {
statusData["stat"] = map[string]interface{}{
statusData["stat"] = map[string]any{
"packet_received": raw.Status.Stat.PacketReceived,
"packet_sent": raw.Status.Stat.PacketSent,
"packet_lost": raw.Status.Stat.PacketLost,
@@ -183,7 +183,7 @@ func (a *Adapter) convertMetaEvent(raw *RawEvent, base *protocol.BaseEvent) (pro
}
// parseMessageSegments 解析消息段
func (a *Adapter) parseMessageSegments(message interface{}) ([]MessageSegment, error) {
func (a *Adapter) parseMessageSegments(message any) ([]MessageSegment, error) {
if message == nil {
return nil, fmt.Errorf("message is nil")
}
@@ -193,7 +193,7 @@ func (a *Adapter) parseMessageSegments(message interface{}) ([]MessageSegment, e
return []MessageSegment{
{
Type: SegmentTypeText,
Data: map[string]interface{}{
Data: map[string]any{
"text": str,
},
},
@@ -215,7 +215,7 @@ func (a *Adapter) parseMessageSegments(message interface{}) ([]MessageSegment, e
}
// BuildMessage 构建OneBot11消息
func BuildMessage(segments []MessageSegment) interface{} {
func BuildMessage(segments []MessageSegment) any {
if len(segments) == 0 {
return ""
}
@@ -235,7 +235,7 @@ func BuildTextMessage(text string) []MessageSegment {
return []MessageSegment{
{
Type: SegmentTypeText,
Data: map[string]interface{}{
Data: map[string]any{
"text": text,
},
},
@@ -247,7 +247,7 @@ func BuildImageMessage(file string) []MessageSegment {
return []MessageSegment{
{
Type: SegmentTypeImage,
Data: map[string]interface{}{
Data: map[string]any{
"file": file,
},
},
@@ -258,7 +258,7 @@ func BuildImageMessage(file string) []MessageSegment {
func BuildAtMessage(userID int64) MessageSegment {
return MessageSegment{
Type: SegmentTypeAt,
Data: map[string]interface{}{
Data: map[string]any{
"qq": userID,
},
}
@@ -268,7 +268,7 @@ func BuildAtMessage(userID int64) MessageSegment {
func BuildReplyMessage(messageID int32) MessageSegment {
return MessageSegment{
Type: SegmentTypeReply,
Data: map[string]interface{}{
Data: map[string]any{
"id": messageID,
},
}
@@ -278,7 +278,7 @@ func BuildReplyMessage(messageID int32) MessageSegment {
func BuildFaceMessage(faceID int) MessageSegment {
return MessageSegment{
Type: SegmentTypeFace,
Data: map[string]interface{}{
Data: map[string]any{
"id": faceID,
},
}
@@ -288,7 +288,7 @@ func BuildFaceMessage(faceID int) MessageSegment {
func BuildRecordMessage(file string) MessageSegment {
return MessageSegment{
Type: SegmentTypeRecord,
Data: map[string]interface{}{
Data: map[string]any{
"file": file,
},
}
@@ -298,7 +298,7 @@ func BuildRecordMessage(file string) MessageSegment {
func BuildVideoMessage(file string) MessageSegment {
return MessageSegment{
Type: SegmentTypeVideo,
Data: map[string]interface{}{
Data: map[string]any{
"file": file,
},
}
@@ -308,7 +308,7 @@ func BuildVideoMessage(file string) MessageSegment {
func BuildShareMessage(url, title string) MessageSegment {
return MessageSegment{
Type: SegmentTypeShare,
Data: map[string]interface{}{
Data: map[string]any{
"url": url,
"title": title,
},
@@ -319,7 +319,7 @@ func BuildShareMessage(url, title string) MessageSegment {
func BuildLocationMessage(lat, lon float64, title, content string) MessageSegment {
return MessageSegment{
Type: SegmentTypeLocation,
Data: map[string]interface{}{
Data: map[string]any{
"lat": lat,
"lon": lon,
"title": title,
@@ -332,7 +332,7 @@ func BuildLocationMessage(lat, lon float64, title, content string) MessageSegmen
func BuildMusicMessage(musicType, musicID string) MessageSegment {
return MessageSegment{
Type: SegmentTypeMusic,
Data: map[string]interface{}{
Data: map[string]any{
"type": musicType,
"id": musicID,
},
@@ -343,7 +343,7 @@ func BuildMusicMessage(musicType, musicID string) MessageSegment {
func BuildCustomMusicMessage(url, audio, title, content, image string) MessageSegment {
return MessageSegment{
Type: SegmentTypeMusic,
Data: map[string]interface{}{
Data: map[string]any{
"type": "custom",
"url": url,
"audio": audio,

View File

@@ -7,7 +7,7 @@ import (
)
// ConvertMessageChainToOB11 将通用消息链转换为 OneBot11 格式
func ConvertMessageChainToOB11(chain protocol.MessageChain) interface{} {
func ConvertMessageChainToOB11(chain protocol.MessageChain) any {
if len(chain) == 0 {
return ""
}
@@ -38,7 +38,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
// 文本消息段
return &MessageSegment{
Type: SegmentTypeText,
Data: map[string]interface{}{
Data: map[string]any{
"text": seg.Data["text"],
},
}
@@ -48,7 +48,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
userID := seg.Data["user_id"]
return &MessageSegment{
Type: SegmentTypeAt,
Data: map[string]interface{}{
Data: map[string]any{
"qq": userID,
},
}
@@ -61,7 +61,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
}
return &MessageSegment{
Type: SegmentTypeAt,
Data: map[string]interface{}{
Data: map[string]any{
"qq": userID,
},
}
@@ -75,7 +75,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
}
return &MessageSegment{
Type: SegmentTypeImage,
Data: map[string]interface{}{
Data: map[string]any{
"file": fileID,
},
}
@@ -88,7 +88,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
}
return &MessageSegment{
Type: SegmentTypeRecord,
Data: map[string]interface{}{
Data: map[string]any{
"file": fileID,
},
}
@@ -101,7 +101,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
}
return &MessageSegment{
Type: SegmentTypeRecord,
Data: map[string]interface{}{
Data: map[string]any{
"file": fileID,
},
}
@@ -114,7 +114,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
}
return &MessageSegment{
Type: SegmentTypeVideo,
Data: map[string]interface{}{
Data: map[string]any{
"file": fileID,
},
}
@@ -124,7 +124,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
messageID := seg.Data["message_id"]
return &MessageSegment{
Type: SegmentTypeReply,
Data: map[string]interface{}{
Data: map[string]any{
"id": messageID,
},
}
@@ -134,7 +134,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
faceID := seg.Data["id"]
return &MessageSegment{
Type: SegmentTypeFace,
Data: map[string]interface{}{
Data: map[string]any{
"id": faceID,
},
}
@@ -149,7 +149,7 @@ func convertSegmentToOB11(seg protocol.MessageSegment) *MessageSegment {
}
// ConvertOB11ToMessageChain 将 OneBot11 消息段转换为通用消息链
func ConvertOB11ToMessageChain(ob11Message interface{}) (protocol.MessageChain, error) {
func ConvertOB11ToMessageChain(ob11Message any) (protocol.MessageChain, error) {
chain := protocol.MessageChain{}
// 如果是字符串,转换为文本消息段
@@ -167,11 +167,11 @@ func ConvertOB11ToMessageChain(ob11Message interface{}) (protocol.MessageChain,
}
// 如果是接口数组,尝试转换
if segments, ok := ob11Message.([]interface{}); ok {
if segments, ok := ob11Message.([]any); ok {
for _, seg := range segments {
if segMap, ok := seg.(map[string]interface{}); ok {
if segMap, ok := seg.(map[string]any); ok {
segType, _ := segMap["type"].(string)
segData, _ := segMap["data"].(map[string]interface{})
segData, _ := segMap["data"].(map[string]any)
genericSeg := convertOB11SegmentToGeneric(MessageSegment{
Type: segType,
Data: segData,
@@ -211,7 +211,7 @@ func convertOB11SegmentToGeneric(seg MessageSegment) protocol.MessageSegment {
}
return protocol.MessageSegment{
Type: protocol.SegmentTypeVoice,
Data: map[string]interface{}{
Data: map[string]any{
"file_id": fileID,
},
}
@@ -223,7 +223,7 @@ func convertOB11SegmentToGeneric(seg MessageSegment) protocol.MessageSegment {
case SegmentTypeFace:
return protocol.MessageSegment{
Type: protocol.SegmentTypeFace,
Data: map[string]interface{}{
Data: map[string]any{
"id": seg.Data["id"],
},
}

View File

@@ -2,32 +2,32 @@ package onebot11
// RawEvent OneBot11原始事件
type RawEvent struct {
Time int64 `json:"time"`
SelfID int64 `json:"self_id"`
PostType string `json:"post_type"`
MessageType string `json:"message_type,omitempty"`
SubType string `json:"sub_type,omitempty"`
MessageID int32 `json:"message_id,omitempty"`
UserID int64 `json:"user_id,omitempty"`
GroupID int64 `json:"group_id,omitempty"`
Message interface{} `json:"message,omitempty"`
RawMessage string `json:"raw_message,omitempty"`
Font int32 `json:"font,omitempty"`
Sender *Sender `json:"sender,omitempty"`
Anonymous *Anonymous `json:"anonymous,omitempty"`
NoticeType string `json:"notice_type,omitempty"`
OperatorID int64 `json:"operator_id,omitempty"`
Duration int64 `json:"duration,omitempty"`
RequestType string `json:"request_type,omitempty"`
Comment string `json:"comment,omitempty"`
Flag string `json:"flag,omitempty"`
MetaType string `json:"meta_event_type,omitempty"`
Status *Status `json:"status,omitempty"`
Interval int64 `json:"interval,omitempty"`
File *FileInfo `json:"file,omitempty"` // 群文件上传信息
TargetID int64 `json:"target_id,omitempty"` // 戳一戳、红包运气王目标ID
HonorType string `json:"honor_type,omitempty"` // 群荣誉类型
Extra map[string]interface{} `json:"-"`
Time int64 `json:"time"`
SelfID int64 `json:"self_id"`
PostType string `json:"post_type"`
MessageType string `json:"message_type,omitempty"`
SubType string `json:"sub_type,omitempty"`
MessageID int32 `json:"message_id,omitempty"`
UserID int64 `json:"user_id,omitempty"`
GroupID int64 `json:"group_id,omitempty"`
Message any `json:"message,omitempty"`
RawMessage string `json:"raw_message,omitempty"`
Font int32 `json:"font,omitempty"`
Sender *Sender `json:"sender,omitempty"`
Anonymous *Anonymous `json:"anonymous,omitempty"`
NoticeType string `json:"notice_type,omitempty"`
OperatorID int64 `json:"operator_id,omitempty"`
Duration int64 `json:"duration,omitempty"`
RequestType string `json:"request_type,omitempty"`
Comment string `json:"comment,omitempty"`
Flag string `json:"flag,omitempty"`
MetaType string `json:"meta_event_type,omitempty"`
Status *Status `json:"status,omitempty"`
Interval int64 `json:"interval,omitempty"`
File *FileInfo `json:"file,omitempty"` // 群文件上传信息
TargetID int64 `json:"target_id,omitempty"` // 戳一戳、红包运气王目标ID
HonorType string `json:"honor_type,omitempty"` // 群荣誉类型
Extra map[string]any `json:"-"`
}
// Sender 发送者信息
@@ -79,23 +79,23 @@ type Stat struct {
// OB11Action OneBot11动作
type OB11Action struct {
Action string `json:"action"`
Params map[string]interface{} `json:"params"`
Echo string `json:"echo,omitempty"`
Action string `json:"action"`
Params map[string]any `json:"params"`
Echo string `json:"echo,omitempty"`
}
// OB11Response OneBot11响应
type OB11Response struct {
Status string `json:"status"`
RetCode int `json:"retcode"`
Data map[string]interface{} `json:"data,omitempty"`
Echo string `json:"echo,omitempty"`
Status string `json:"status"`
RetCode int `json:"retcode"`
Data map[string]any `json:"data,omitempty"`
Echo string `json:"echo,omitempty"`
}
// MessageSegment 消息段
type MessageSegment struct {
Type string `json:"type"`
Data map[string]interface{} `json:"data"`
Type string `json:"type"`
Data map[string]any `json:"data"`
}
// 消息段类型常量

View File

@@ -149,22 +149,9 @@ func ProvideMilkyBots(cfg *config.Config, logger *zap.Logger, eventBus *engine.E
bot := milky.NewBot(botCfg.ID, milkyCfg, eventBus, wsManager, logger)
botManager.Add(bot)
// 注意:不在这里启动连接,由 RegisterLifecycleHooks 中的 botManager.StartAll() 统一启动
// 这样避免重复启动的问题
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
logger.Info("Starting Milky bot", zap.String("bot_id", botCfg.ID))
// 在后台启动连接,失败时只记录错误,不终止应用
go func() {
if err := bot.Connect(context.Background()); err != nil {
logger.Error("Failed to connect Milky bot, will retry in background",
zap.String("bot_id", botCfg.ID),
zap.Error(err))
// 可以在这里实现重试逻辑
} else {
logger.Info("Milky bot connected successfully", zap.String("bot_id", botCfg.ID))
}
}()
return nil
},
OnStop: func(ctx context.Context) error {
logger.Info("Stopping Milky bot", zap.String("bot_id", botCfg.ID))
return bot.Disconnect(ctx)
@@ -200,22 +187,9 @@ func ProvideOneBot11Bots(cfg *config.Config, logger *zap.Logger, wsManager *net.
bot := onebot11.NewBot(botCfg.ID, ob11Cfg, logger, wsManager, eventBus)
botManager.Add(bot)
// 注意:不在这里启动连接,由 RegisterLifecycleHooks 中的 botManager.StartAll() 统一启动
// 这样避免重复启动的问题
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
logger.Info("Starting OneBot11 bot", zap.String("bot_id", botCfg.ID))
// 在后台启动连接,失败时只记录错误,不终止应用
go func() {
if err := bot.Connect(context.Background()); err != nil {
logger.Error("Failed to connect OneBot11 bot, will retry in background",
zap.String("bot_id", botCfg.ID),
zap.Error(err))
// 可以在这里实现重试逻辑
} else {
logger.Info("OneBot11 bot connected successfully", zap.String("bot_id", botCfg.ID))
}
}()
return nil
},
OnStop: func(ctx context.Context) error {
logger.Info("Stopping OneBot11 bot", zap.String("bot_id", botCfg.ID))
return bot.Disconnect(ctx)

View File

@@ -3,6 +3,7 @@ package engine
import (
"context"
"runtime/debug"
"slices"
"sort"
"sync"
"sync/atomic"
@@ -238,8 +239,7 @@ func (d *Dispatcher) handleEvent(ctx context.Context, event protocol.Event) {
func (d *Dispatcher) createHandlerChain(ctx context.Context, event protocol.Event) func(context.Context, protocol.Event) {
return func(ctx context.Context, e protocol.Event) {
d.mu.RLock()
handlers := make([]protocol.EventHandler, len(d.handlers))
copy(handlers, d.handlers)
handlers := slices.Clone(d.handlers)
d.mu.RUnlock()
for i, handler := range handlers {

View File

@@ -4,6 +4,7 @@ import (
"context"
"crypto/rand"
"encoding/hex"
"slices"
"sync"
"sync/atomic"
"time"
@@ -63,8 +64,7 @@ func NewEventBus(logger *zap.Logger, bufferSize int) *EventBus {
// Start 启动事件总线
func (eb *EventBus) Start() {
eb.wg.Add(1)
go eb.dispatch()
eb.wg.Go(eb.dispatch)
eb.logger.Info("Event bus started")
}
@@ -193,8 +193,6 @@ func (eb *EventBus) Unsubscribe(eventType protocol.EventType, ch chan protocol.E
// dispatch 分发事件到订阅者
func (eb *EventBus) dispatch() {
defer eb.wg.Done()
eb.logger.Info("Event bus dispatch loop started")
for {
@@ -220,8 +218,7 @@ func (eb *EventBus) dispatchEvent(event protocol.Event) {
key := string(event.GetType())
subs := eb.subscriptions[key]
// 复制订阅者列表避免锁竞争
subsCopy := make([]*Subscription, len(subs))
copy(subsCopy, subs)
subsCopy := slices.Clone(subs)
eb.mu.RUnlock()
eb.logger.Info("Dispatching event",

View File

@@ -260,14 +260,14 @@ func (m *MetricsMiddleware) Process(ctx context.Context, event protocol.Event, n
}
// GetMetrics 获取指标
func (m *MetricsMiddleware) GetMetrics() map[string]interface{} {
func (m *MetricsMiddleware) GetMetrics() map[string]any {
m.mu.RLock()
defer m.mu.RUnlock()
metrics := make(map[string]interface{})
metrics := make(map[string]any)
for eventType, count := range m.eventCounts {
avgTime := m.eventTimes[eventType] / time.Duration(count)
metrics[eventType] = map[string]interface{}{
metrics[eventType] = map[string]any{
"count": count,
"avg_time": avgTime.String(),
}

View File

@@ -599,12 +599,12 @@ func OnlyToMe() HandlerMiddleware {
selfID := event.GetSelfID()
// 检查消息段中是否包含@机器人的消息
if segments, ok := data["message_segments"].([]interface{}); ok {
if segments, ok := data["message_segments"].([]any); ok {
for _, seg := range segments {
if segMap, ok := seg.(map[string]interface{}); ok {
if segMap, ok := seg.(map[string]any); ok {
segType, _ := segMap["type"].(string)
if segType == "at" || segType == "mention" {
segData, _ := segMap["data"].(map[string]interface{})
segData, _ := segMap["data"].(map[string]any)
// 检查是否@了机器人
if userID, ok := segData["user_id"]; ok {
if userIDStr := fmt.Sprintf("%v", userID); userIDStr == selfID {

View File

@@ -329,15 +329,13 @@ func (j *IntervalJob) Start(ctx context.Context) error {
j.nextRun = time.Now().Add(j.interval)
j.mu.Unlock()
j.wg.Add(1)
go j.run()
j.wg.Go(j.run)
j.logger.Info("Interval job started", zap.Duration("interval", j.interval))
return nil
}
func (j *IntervalJob) run() {
defer j.wg.Done()
// 立即执行一次(可选,根据需求调整)
// if err := j.handler(j.ctx); err != nil {
@@ -428,16 +426,13 @@ func (j *OnceJob) Start(ctx context.Context) error {
j.nextRun = time.Now().Add(j.delay)
j.mu.Unlock()
j.wg.Add(1)
go j.run()
j.wg.Go(j.run)
j.logger.Info("Once job started", zap.Duration("delay", j.delay))
return nil
}
func (j *OnceJob) run() {
defer j.wg.Done()
select {
case <-j.timer.C:
if err := j.handler(j.ctx); err != nil {

View File

@@ -192,7 +192,7 @@ func handleMCSBindCommand(ctx context.Context, event protocol.Event, botManager
action := &protocol.BaseAction{
Type: protocol.ActionTypeSendGroupMessage,
Params: map[string]interface{}{
Params: map[string]any{
"group_id": groupID,
"message": errorMsg,
},

View File

@@ -76,7 +76,7 @@ func Ping(host string, port int, timeout time.Duration) (*ServerStatus, error) {
}
// 解析 JSON 响应
var statusData map[string]interface{}
var statusData map[string]any
if err := json.Unmarshal([]byte(statusJSON), &statusData); err != nil {
return nil, fmt.Errorf("failed to parse JSON: %w", err)
}
@@ -88,14 +88,14 @@ func Ping(host string, port int, timeout time.Duration) (*ServerStatus, error) {
}
// 解析 description
if desc, ok := statusData["description"].(map[string]interface{}); ok {
if desc, ok := statusData["description"].(map[string]any); ok {
if text, ok := desc["text"].(string); ok {
status.Description = text
} else if text, ok := desc["extra"].([]interface{}); ok && len(text) > 0 {
} else if text, ok := desc["extra"].([]any); ok && len(text) > 0 {
// 处理 extra 数组
var descText strings.Builder
for _, item := range text {
if itemMap, ok := item.(map[string]interface{}); ok {
if itemMap, ok := item.(map[string]any); ok {
if text, ok := itemMap["text"].(string); ok {
descText.WriteString(text)
}
@@ -108,7 +108,7 @@ func Ping(host string, port int, timeout time.Duration) (*ServerStatus, error) {
}
// 解析 version
if version, ok := statusData["version"].(map[string]interface{}); ok {
if version, ok := statusData["version"].(map[string]any); ok {
if name, ok := version["name"].(string); ok {
status.Version.Name = name
}
@@ -118,7 +118,7 @@ func Ping(host string, port int, timeout time.Duration) (*ServerStatus, error) {
}
// 解析 players
if players, ok := statusData["players"].(map[string]interface{}); ok {
if players, ok := statusData["players"].(map[string]any); ok {
if online, ok := players["online"].(float64); ok {
status.Players.Online = int(online)
}

View File

@@ -49,7 +49,7 @@ func handleWelcomeEvent(ctx context.Context, event protocol.Event, botManager *p
zap.Any("user_id", userID))
// 获取操作者ID邀请者或审批者
var operatorID interface{}
var operatorID any
if opID, exists := data["operator_id"]; exists {
operatorID = opID
}
@@ -95,7 +95,7 @@ func handleWelcomeEvent(ctx context.Context, event protocol.Event, botManager *p
action := &protocol.BaseAction{
Type: protocol.ActionTypeSendGroupMessage,
Params: map[string]interface{}{
Params: map[string]any{
"group_id": groupID,
"message": welcomeChain,
},
@@ -285,14 +285,14 @@ const welcomeTemplate = `
`
// buildWelcomeMessage 构建欢迎消息链使用HTML模板渲染图片
func buildWelcomeMessage(ctx context.Context, userID, operatorID interface{}, subType string, logger *zap.Logger) (protocol.MessageChain, error) {
func buildWelcomeMessage(ctx context.Context, userID, operatorID any, subType string, logger *zap.Logger) (protocol.MessageChain, error) {
logger.Debug("Starting to build welcome message",
zap.Any("user_id", userID),
zap.Any("operator_id", operatorID),
zap.String("sub_type", subType))
// 准备模板数据
data := map[string]interface{}{
data := map[string]any{
"UserID": fmt.Sprintf("%v", userID),
}
logger.Debug("Template data prepared", zap.Any("data", data))

View File

@@ -6,9 +6,9 @@ type Action interface {
// GetType 获取动作类型
GetType() ActionType
// GetParams 获取动作参数
GetParams() map[string]interface{}
GetParams() map[string]any
// Execute 执行动作
Execute(ctx interface{}) (map[string]interface{}, error)
Execute(ctx any) (map[string]any, error)
}
// ActionType 动作类型
@@ -27,20 +27,20 @@ const (
ActionTypeGetGroupMemberList ActionType = "get_group_member_list"
// 群组相关动作
ActionTypeSetGroupKick ActionType = "set_group_kick"
ActionTypeSetGroupBan ActionType = "set_group_ban"
ActionTypeSetGroupAdmin ActionType = "set_group_admin"
ActionTypeSetGroupWholeBan ActionType = "set_group_whole_ban"
ActionTypeSetGroupKick ActionType = "set_group_kick"
ActionTypeSetGroupBan ActionType = "set_group_ban"
ActionTypeSetGroupAdmin ActionType = "set_group_admin"
ActionTypeSetGroupWholeBan ActionType = "set_group_whole_ban"
// 其他动作
ActionTypeGetStatus ActionType = "get_status"
ActionTypeGetVersion ActionType = "get_version"
ActionTypeGetStatus ActionType = "get_status"
ActionTypeGetVersion ActionType = "get_version"
)
// BaseAction 基础动作结构
type BaseAction struct {
Type ActionType `json:"type"`
Params map[string]interface{} `json:"params"`
Type ActionType `json:"type"`
Params map[string]any `json:"params"`
}
// GetType 获取动作类型
@@ -49,19 +49,19 @@ func (a *BaseAction) GetType() ActionType {
}
// GetParams 获取动作参数
func (a *BaseAction) GetParams() map[string]interface{} {
func (a *BaseAction) GetParams() map[string]any {
return a.Params
}
// Execute 执行动作(需子类实现)
func (a *BaseAction) Execute(ctx interface{}) (map[string]interface{}, error) {
func (a *BaseAction) Execute(ctx any) (map[string]any, error) {
return nil, ErrNotImplemented
}
// SendPrivateMessageAction 发送私聊消息动作
type SendPrivateMessageAction struct {
BaseAction
UserID string `json:"user_id"`
UserID string `json:"user_id"`
Message string `json:"message"`
}

View File

@@ -91,7 +91,7 @@ func (b *BaseBotInstance) IsConnected() bool {
}
// SendAction 发送动作
func (b *BaseBotInstance) SendAction(ctx context.Context, action Action) (map[string]interface{}, error) {
func (b *BaseBotInstance) SendAction(ctx context.Context, action Action) (map[string]any, error) {
return b.protocol.SendAction(ctx, action)
}

View File

@@ -35,7 +35,7 @@ type Event interface {
// GetSelfID 获取机器人自身ID
GetSelfID() string
// GetData 获取事件数据
GetData() map[string]interface{}
GetData() map[string]any
// Reply 在消息发生的群/私聊进行回复
Reply(ctx context.Context, botManager *BotManager, logger *zap.Logger, message MessageChain) error
// ReplyText 在消息发生的群/私聊进行文本回复(便捷方法)
@@ -44,12 +44,12 @@ type Event interface {
// BaseEvent 基础事件结构
type BaseEvent struct {
Type EventType `json:"type"`
DetailType string `json:"detail_type"`
SubType string `json:"sub_type,omitempty"`
Timestamp int64 `json:"timestamp"`
SelfID string `json:"self_id"`
Data map[string]interface{} `json:"data"`
Type EventType `json:"type"`
DetailType string `json:"detail_type"`
SubType string `json:"sub_type,omitzero"`
Timestamp int64 `json:"timestamp"`
SelfID string `json:"self_id"`
Data map[string]any `json:"data"`
}
// GetType 获取事件类型
@@ -78,7 +78,7 @@ func (e *BaseEvent) GetSelfID() string {
}
// GetData 获取事件数据
func (e *BaseEvent) GetData() map[string]interface{} {
func (e *BaseEvent) GetData() map[string]any {
return e.Data
}
@@ -105,7 +105,7 @@ func (e *BaseEvent) Reply(ctx context.Context, botManager *BotManager, logger *z
if e.GetDetailType() == "private" {
action = &BaseAction{
Type: ActionTypeSendPrivateMessage,
Params: map[string]interface{}{
Params: map[string]any{
"user_id": userID,
"message": message,
},
@@ -114,7 +114,7 @@ func (e *BaseEvent) Reply(ctx context.Context, botManager *BotManager, logger *z
// 群聊或其他有 group_id 的事件
action = &BaseAction{
Type: ActionTypeSendGroupMessage,
Params: map[string]interface{}{
Params: map[string]any{
"group_id": groupID,
"message": message,
},
@@ -151,15 +151,15 @@ type MessageEvent struct {
BaseEvent
MessageID string `json:"message_id"`
Message string `json:"message"`
AltText string `json:"alt_text,omitempty"`
AltText string `json:"alt_text,omitzero"`
}
// NoticeEvent 通知事件
type NoticeEvent struct {
BaseEvent
GroupID string `json:"group_id,omitempty"`
UserID string `json:"user_id,omitempty"`
Operator string `json:"operator,omitempty"`
GroupID string `json:"group_id,omitzero"`
UserID string `json:"user_id,omitzero"`
Operator string `json:"operator,omitzero"`
}
// RequestEvent 请求事件

View File

@@ -18,7 +18,7 @@ type Protocol interface {
// IsConnected 检查连接状态
IsConnected() bool
// SendAction 发送动作
SendAction(ctx context.Context, action Action) (map[string]interface{}, error)
SendAction(ctx context.Context, action Action) (map[string]any, error)
// HandleEvent 处理事件
HandleEvent(ctx context.Context, event Event) error
// GetSelfID 获取机器人自身ID

View File

@@ -5,8 +5,8 @@ import "fmt"
// MessageSegment 消息段(基于 OneBot12 设计)
// 通用消息段结构,适配器负责转换为具体协议格式
type MessageSegment struct {
Type string `json:"type"` // 消息段类型
Data map[string]interface{} `json:"data"` // 消息段数据
Type string `json:"type"` // 消息段类型
Data map[string]any `json:"data"` // 消息段数据
}
// MessageChain 消息链(基于 OneBot12 设计)
@@ -33,17 +33,17 @@ const (
func NewTextSegment(text string) MessageSegment {
return MessageSegment{
Type: SegmentTypeText,
Data: map[string]interface{}{
Data: map[string]any{
"text": text,
},
}
}
// NewMentionSegment 创建@提及消息段OneBot12 标准)
func NewMentionSegment(userID interface{}) MessageSegment {
func NewMentionSegment(userID any) MessageSegment {
return MessageSegment{
Type: SegmentTypeMention,
Data: map[string]interface{}{
Data: map[string]any{
"user_id": userID,
},
}
@@ -53,7 +53,7 @@ func NewMentionSegment(userID interface{}) MessageSegment {
func NewImageSegment(fileID string) MessageSegment {
return MessageSegment{
Type: SegmentTypeImage,
Data: map[string]interface{}{
Data: map[string]any{
"file_id": fileID,
},
}
@@ -63,17 +63,17 @@ func NewImageSegment(fileID string) MessageSegment {
func NewImageSegmentFromBase64(base64Data string) MessageSegment {
return MessageSegment{
Type: SegmentTypeImage,
Data: map[string]interface{}{
Data: map[string]any{
"file": fmt.Sprintf("base64://%s", base64Data),
},
}
}
// NewReplySegment 创建回复消息段
func NewReplySegment(messageID interface{}) MessageSegment {
func NewReplySegment(messageID any) MessageSegment {
return MessageSegment{
Type: SegmentTypeReply,
Data: map[string]interface{}{
Data: map[string]any{
"message_id": messageID,
},
}
@@ -95,7 +95,7 @@ func (mc MessageChain) AppendText(text string) MessageChain {
}
// AppendMention 追加@提及到消息链
func (mc MessageChain) AppendMention(userID interface{}) MessageChain {
func (mc MessageChain) AppendMention(userID any) MessageChain {
return mc.Append(NewMentionSegment(userID))
}