napcat: NapCatQQ (OneBot 11) Go SDK, initial release

This commit is contained in:
2026-10-18 18:37:07 +08:00
commit 48d56c64d0
14 changed files with 2879 additions and 0 deletions

360
types.go Normal file
View File

@ -0,0 +1,360 @@
package napcat
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
)
// ErrNotConnected 表示当前没有任何可用的 OneBot 连接来执行 API 调用。
var ErrNotConnected = errors.New("napcat: no connected session")
// Response 是 OneBot 11 的通用动作响应信封。
type Response struct {
Status string `json:"status"`
Retcode int `json:"retcode"`
Data json.RawMessage `json:"data"`
Echo string `json:"echo"`
Message string `json:"message,omitempty"`
Wording string `json:"wording,omitempty"`
}
// Err 在响应失败时返回非 nil 错误。
func (r *Response) Err() error {
if r == nil || r.Retcode == 0 || r.Status == "ok" || r.Status == "async" {
return nil
}
msg := r.Wording
if msg == "" {
msg = r.Message
}
if msg == "" {
msg = "unknown error"
}
return fmt.Errorf("napcat: action failed retcode=%d status=%s: %s", r.Retcode, r.Status, msg)
}
// Decode 将响应的 data 字段解码到 out。
func (r *Response) Decode(out any) error {
if r == nil || out == nil {
return nil
}
if len(r.Data) == 0 || string(r.Data) == "null" {
return nil
}
return json.Unmarshal(r.Data, out)
}
// IntString 兼容数字或字符串形式出现的整数字段(如群成员 level)。
type IntString int64
func (v *IntString) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), `"`)
if s == "" || s == "null" {
*v = 0
return nil
}
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return fmt.Errorf("napcat: invalid integer %q: %w", s, err)
}
*v = IntString(n)
return nil
}
// Sender 是消息事件的发送者信息。群聊时含 Card/Role,私聊时可能为空。
// 注意:NapCat v4 的消息事件 sender 是精简的,实测不含 Role 字段
// (完整角色需调 GetGroupMemberInfo 查询);本结构保留 Role 以兼容
// 标准 OneBot 11 实现和需要 role 的接口返回。
type Sender struct {
UserID int64 `json:"user_id"`
Nickname string `json:"nickname"`
Sex string `json:"sex,omitempty"`
Card string `json:"card,omitempty"`
Role string `json:"role,omitempty"`
}
type LoginInfo struct {
UserID int64 `json:"user_id"`
Nickname string `json:"nickname"`
}
type StrangerInfo struct {
UserID int64 `json:"user_id"`
Nickname string `json:"nickname"`
Sex string `json:"sex,omitempty"`
Age int64 `json:"age,omitempty"`
QID string `json:"qid,omitempty"`
LongNick string `json:"long_nick,omitempty"`
Level int64 `json:"level,omitempty"`
}
type FriendInfo struct {
UserID int64 `json:"user_id"`
Nickname string `json:"nickname"`
Remark string `json:"remark"`
Sex string `json:"sex,omitempty"`
Level int64 `json:"level,omitempty"`
}
type GroupInfo struct {
GroupID int64 `json:"group_id"`
GroupName string `json:"group_name"`
MemberCount int64 `json:"member_count"`
MaxMemberCount int64 `json:"max_member_count"`
}
type GroupMemberInfo struct {
GroupID int64 `json:"group_id"`
UserID int64 `json:"user_id"`
Nickname string `json:"nickname"`
Card string `json:"card"`
CardChangeable bool `json:"card_changeable"`
Sex string `json:"sex"`
Level IntString `json:"level"`
Role string `json:"role"`
Title string `json:"title"`
JoinTime int64 `json:"join_time"`
LastSentTime int64 `json:"last_sent_time"`
ShutUpTimestamp int64 `json:"shut_up_timestamp"`
Unfriendly bool `json:"unfriendly"`
}
type Status struct {
Online bool `json:"online"`
Good bool `json:"good"`
}
type VersionInfo struct {
AppName string `json:"app_name"`
AppVersion string `json:"app_version"`
ProtocolVersion string `json:"protocol_version"`
}
// MessageInfo 是 get_msg 等 API 返回的单条消息。
type MessageInfo struct {
MessageID int64 `json:"message_id"`
RealID int64 `json:"real_id"`
MessageSeq int64 `json:"message_seq,omitempty"`
Time int64 `json:"time"`
Sender Sender `json:"sender"`
Message json.RawMessage `json:"message"`
RawMessage string `json:"raw_message"`
Font int64 `json:"font,omitempty"`
GroupID int64 `json:"group_id,omitempty"`
}
// Segments 将 message 字段解码为消息段数组。
func (m MessageInfo) Segments() ([]Segment, error) { return DecodeMessage(m.Message) }
// HistoryMessage 是历史消息接口(get_group_msg_history 等)返回的单条消息。
type HistoryMessage struct {
MessageID int64 `json:"message_id"`
MessageSeq int64 `json:"message_seq"`
Time int64 `json:"time"`
Sender Sender `json:"sender"`
Message json.RawMessage `json:"message"`
RawMessage string `json:"raw_message"`
GroupID int64 `json:"group_id,omitempty"`
}
func (m HistoryMessage) Segments() ([]Segment, error) { return DecodeMessage(m.Message) }
// HistoryMessages 兼容 data 为 {"messages": [...]} 或直接数组两种返回形态。
type HistoryMessages []HistoryMessage
func (h *HistoryMessages) UnmarshalJSON(b []byte) error {
var probe struct {
Messages []HistoryMessage `json:"messages"`
}
if err := json.Unmarshal(b, &probe); err == nil && probe.Messages != nil {
*h = probe.Messages
return nil
}
var arr []HistoryMessage
if err := json.Unmarshal(b, &arr); err == nil {
*h = arr
return nil
}
return fmt.Errorf("napcat: unexpected history message payload: %s", truncate(b))
}
// ForwardMessage 是合并转发中的一条消息。
type ForwardMessage struct {
MessageID int64 `json:"message_id"`
Time int64 `json:"time"`
Sender Sender `json:"sender"`
Message json.RawMessage `json:"message"`
Content json.RawMessage `json:"content,omitempty"`
RawMessage string `json:"raw_message"`
}
// Segments 将消息体解码为消息段数组,优先取 message 字段,其次 content。
func (m ForwardMessage) Segments() ([]Segment, error) {
if len(m.Message) > 0 {
return DecodeMessage(m.Message)
}
return DecodeMessage(m.Content)
}
// ForwardMessages 兼容 data 为 {"messages": [...]} 或直接数组两种返回形态。
type ForwardMessages []ForwardMessage
func (f *ForwardMessages) UnmarshalJSON(b []byte) error {
var probe struct {
Messages []ForwardMessage `json:"messages"`
}
if err := json.Unmarshal(b, &probe); err == nil && probe.Messages != nil {
*f = probe.Messages
return nil
}
var arr []ForwardMessage
if err := json.Unmarshal(b, &arr); err == nil {
*f = arr
return nil
}
return fmt.Errorf("napcat: unexpected forward message payload: %s", truncate(b))
}
// SendForwardResult 是发送合并转发消息的返回。
type SendForwardResult struct {
MessageID IntString `json:"message_id"`
ResID string `json:"res_id"`
}
type EssenceMessage struct {
GroupID int64 `json:"group_id"`
SenderID int64 `json:"sender_id"`
OperatorID int64 `json:"operator_id"`
Time int64 `json:"time"`
MessageID int64 `json:"message_id"`
Message json.RawMessage `json:"message,omitempty"`
RawMessage string `json:"raw_message,omitempty"`
}
type GroupFile struct {
FileID string `json:"file_id"`
FileName string `json:"file_name"`
BusID int64 `json:"busid"`
FileSize int64 `json:"file_size"`
UploadTime int64 `json:"upload_time"`
DeadTime int64 `json:"dead_time"`
ModifyTime int64 `json:"modify_time"`
DownloadTimes int64 `json:"download_times"`
Uploader string `json:"uploader,omitempty"`
UploaderID int64 `json:"uploader_id,omitempty"`
}
type GroupFolder struct {
FolderID string `json:"folder_id"`
FolderName string `json:"folder_name"`
CreateTime int64 `json:"create_time"`
Creator string `json:"creator,omitempty"`
CreatorID int64 `json:"creator_id,omitempty"`
TotalFileCount int64 `json:"total_file_count"`
}
type FileList struct {
Files []GroupFile `json:"files"`
Folders []GroupFolder `json:"folders"`
}
type FileSystemInfo struct {
FileCount int64 `json:"file_count"`
LimitCount int64 `json:"limit_count"`
UsedSpace int64 `json:"used_space"`
TotalSpace int64 `json:"total_space"`
}
type ImageResult struct {
File string `json:"file"`
Filename string `json:"filename,omitempty"`
Size int64 `json:"size,omitempty"`
URL string `json:"url,omitempty"`
}
type RecordResult struct {
File string `json:"file"`
URL string `json:"url,omitempty"`
}
type DownloadedFile struct {
File string `json:"file"`
URL string `json:"url,omitempty"`
FileName string `json:"file_name,omitempty"`
FileSize int64 `json:"file_size,omitempty"`
}
type Cookies struct {
Cookies string `json:"cookies"`
}
type CSRFToken struct {
Token string `json:"token"`
}
type Credentials struct {
Cookies string `json:"cookies"`
CSRFToken string `json:"csrf_token"`
}
type URLSafety struct {
Level int `json:"level"`
Message string `json:"message,omitempty"`
}
type AtAllRemain struct {
CanAtAll bool `json:"can_at_all"`
RemainAtAllCount int64 `json:"remain_at_all_count"`
RemainAtAllCountForGroup int64 `json:"remain_at_all_count_for_group"`
RemainAtAllCountForUser int64 `json:"remain_at_all_count_for_user"`
}
type RobotRange struct {
MinUin int64 `json:"min_uin"`
MaxUin int64 `json:"max_uin"`
}
// RobotUinRanges 兼容 data 为 {"robot_uin_range": [...]} 或直接数组两种形态。
type RobotUinRanges []RobotRange
func (r *RobotUinRanges) UnmarshalJSON(b []byte) error {
var probe struct {
RobotUinRange []RobotRange `json:"robot_uin_range"`
}
if err := json.Unmarshal(b, &probe); err == nil && probe.RobotUinRange != nil {
*r = probe.RobotUinRange
return nil
}
var arr []RobotRange
if err := json.Unmarshal(b, &arr); err == nil {
*r = arr
return nil
}
return fmt.Errorf("napcat: unexpected robot uin range payload: %s", truncate(b))
}
type OnlineClient struct {
AppID int64 `json:"app_id"`
DeviceName string `json:"device_name"`
DeviceKind string `json:"device_kind"`
}
type OnlineClients struct {
Clients []OnlineClient `json:"clients"`
Online bool `json:"online"`
}
type MiniAppArk struct {
Ark string `json:"ark"`
}
func truncate(b []byte) string {
s := string(b)
if len(s) > 128 {
s = s[:128] + "..."
}
return s
}