mirror of
https://github.com/Mrs4s/go-cqhttp.git
synced 2025-05-04 19:17:37 +08:00
Merge branch 'dev'
This commit is contained in:
commit
2af55d6a67
@ -15,18 +15,17 @@ import (
|
|||||||
"github.com/Mrs4s/MiraiGo/client"
|
"github.com/Mrs4s/MiraiGo/client"
|
||||||
"github.com/Mrs4s/MiraiGo/utils"
|
"github.com/Mrs4s/MiraiGo/utils"
|
||||||
"github.com/Mrs4s/MiraiGo/wrapper"
|
"github.com/Mrs4s/MiraiGo/wrapper"
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/encryption"
|
|
||||||
_ "github.com/Mrs4s/go-cqhttp/internal/encryption/t544"
|
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"gopkg.ilharper.com/x/isatty"
|
"gopkg.ilharper.com/x/isatty"
|
||||||
|
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
|
||||||
|
|
||||||
"github.com/Mrs4s/go-cqhttp/global"
|
"github.com/Mrs4s/go-cqhttp/global"
|
||||||
|
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||||
|
"github.com/Mrs4s/go-cqhttp/internal/encryption"
|
||||||
|
_ "github.com/Mrs4s/go-cqhttp/internal/encryption/t544" // side effect
|
||||||
)
|
)
|
||||||
|
|
||||||
var console = bufio.NewReader(os.Stdin)
|
var console = bufio.NewReader(os.Stdin)
|
||||||
@ -218,8 +217,14 @@ func loginResponseProcessor(res *client.LoginResponse) error {
|
|||||||
case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError:
|
case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError:
|
||||||
msg := res.ErrorMessage
|
msg := res.ErrorMessage
|
||||||
log.Warnf("登录失败: %v Code: %v", msg, res.Code)
|
log.Warnf("登录失败: %v Code: %v", msg, res.Code)
|
||||||
if res.Code == 235 {
|
switch res.Code {
|
||||||
log.Warnf("请删除 device.json 后重试.")
|
case 235:
|
||||||
|
log.Warnf("设备信息被封禁, 请删除 device.json 后重试.")
|
||||||
|
case 237:
|
||||||
|
log.Warnf("登录过于频繁, 请在手机QQ登录并根据提示完成认证后等一段时间重试")
|
||||||
|
case 45: // 在提供 t544 后还是出现45错误是需要强行升级到最新客户端或被限制非常用设备
|
||||||
|
log.Warnf("你的账号涉嫌违规被限制在非常用设备登录, 请在手机QQ登录并根据提示完成认证")
|
||||||
|
log.Warnf("或使用 -update-protocol 升级到最新协议后重试")
|
||||||
}
|
}
|
||||||
log.Infof("按 Enter 继续....")
|
log.Infof("按 Enter 继续....")
|
||||||
readLine()
|
readLine()
|
||||||
|
@ -22,14 +22,13 @@ import (
|
|||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
|
||||||
|
|
||||||
"github.com/Mrs4s/go-cqhttp/coolq"
|
"github.com/Mrs4s/go-cqhttp/coolq"
|
||||||
"github.com/Mrs4s/go-cqhttp/db"
|
"github.com/Mrs4s/go-cqhttp/db"
|
||||||
"github.com/Mrs4s/go-cqhttp/global"
|
"github.com/Mrs4s/go-cqhttp/global"
|
||||||
"github.com/Mrs4s/go-cqhttp/global/terminal"
|
"github.com/Mrs4s/go-cqhttp/global/terminal"
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/cache"
|
"github.com/Mrs4s/go-cqhttp/internal/cache"
|
||||||
|
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/selfdiagnosis"
|
"github.com/Mrs4s/go-cqhttp/internal/selfdiagnosis"
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/selfupdate"
|
"github.com/Mrs4s/go-cqhttp/internal/selfupdate"
|
||||||
"github.com/Mrs4s/go-cqhttp/modules/servers"
|
"github.com/Mrs4s/go-cqhttp/modules/servers"
|
||||||
@ -168,51 +167,48 @@ func LoginInteract() {
|
|||||||
if !global.PathExists("password.encrypt") {
|
if !global.PathExists("password.encrypt") {
|
||||||
if base.Account.Password == "" {
|
if base.Account.Password == "" {
|
||||||
log.Error("无法进行加密,请在配置文件中的添加密码后重新启动.")
|
log.Error("无法进行加密,请在配置文件中的添加密码后重新启动.")
|
||||||
readLine()
|
} else {
|
||||||
os.Exit(0)
|
log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)")
|
||||||
|
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
|
||||||
|
_ = os.WriteFile("password.encrypt", []byte(PasswordHashEncrypt(base.PasswordHash[:], byteKey)), 0o644)
|
||||||
|
log.Info("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
|
||||||
}
|
}
|
||||||
log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)")
|
|
||||||
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
|
|
||||||
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
|
|
||||||
_ = os.WriteFile("password.encrypt", []byte(PasswordHashEncrypt(base.PasswordHash[:], byteKey)), 0o644)
|
|
||||||
log.Info("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
|
|
||||||
readLine()
|
readLine()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
} else {
|
|
||||||
if base.Account.Password != "" {
|
|
||||||
log.Error("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
|
|
||||||
readLine()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(byteKey) == 0 {
|
|
||||||
log.Infof("密码加密已启用, 请输入Key对密码进行解密以继续: (Enter 提交)")
|
|
||||||
cancel := make(chan struct{}, 1)
|
|
||||||
state, _ := term.GetState(int(os.Stdin.Fd()))
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-cancel:
|
|
||||||
return
|
|
||||||
case <-time.After(time.Second * 45):
|
|
||||||
log.Infof("解密key输入超时")
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
_ = term.Restore(int(os.Stdin.Fd()), state)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
|
|
||||||
cancel <- struct{}{}
|
|
||||||
} else {
|
|
||||||
log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.")
|
|
||||||
}
|
|
||||||
|
|
||||||
encrypt, _ := os.ReadFile("password.encrypt")
|
|
||||||
ph, err := PasswordHashDecrypt(string(encrypt), byteKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
|
|
||||||
}
|
|
||||||
copy(base.PasswordHash[:], ph)
|
|
||||||
}
|
}
|
||||||
|
if base.Account.Password != "" {
|
||||||
|
log.Error("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
|
||||||
|
readLine()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
if len(byteKey) == 0 {
|
||||||
|
log.Infof("密码加密已启用, 请输入Key对密码进行解密以继续: (Enter 提交)")
|
||||||
|
cancel := make(chan struct{}, 1)
|
||||||
|
state, _ := term.GetState(int(os.Stdin.Fd()))
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-cancel:
|
||||||
|
return
|
||||||
|
case <-time.After(time.Second * 45):
|
||||||
|
log.Infof("解密key输入超时")
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
_ = term.Restore(int(os.Stdin.Fd()), state)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
cancel <- struct{}{}
|
||||||
|
} else {
|
||||||
|
log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.")
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypt, _ := os.ReadFile("password.encrypt")
|
||||||
|
ph, err := PasswordHashDecrypt(string(encrypt), byteKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
|
||||||
|
}
|
||||||
|
copy(base.PasswordHash[:], ph)
|
||||||
} else if len(base.Account.Password) > 0 {
|
} else if len(base.Account.Password) > 0 {
|
||||||
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
|
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
|
||||||
}
|
}
|
||||||
@ -368,6 +364,7 @@ func LoginInteract() {
|
|||||||
})
|
})
|
||||||
saveToken()
|
saveToken()
|
||||||
cli.AllowSlider = true
|
cli.AllowSlider = true
|
||||||
|
download.SetTimeout(time.Duration(base.HTTPTimeout) * time.Second) // 在登录完成后设置, 防止在堵塞协议更新
|
||||||
log.Infof("登录成功 欢迎使用: %v", cli.Nickname)
|
log.Infof("登录成功 欢迎使用: %v", cli.Nickname)
|
||||||
log.Info("开始加载好友列表...")
|
log.Info("开始加载好友列表...")
|
||||||
global.Check(cli.ReloadFriendList(), true)
|
global.Check(cli.ReloadFriendList(), true)
|
||||||
@ -379,7 +376,6 @@ func LoginInteract() {
|
|||||||
base.Account.Status = 0
|
base.Account.Status = 0
|
||||||
}
|
}
|
||||||
cli.SetOnlineStatus(allowStatus[base.Account.Status])
|
cli.SetOnlineStatus(allowStatus[base.Account.Status])
|
||||||
|
|
||||||
servers.Run(coolq.NewQQBot(cli))
|
servers.Run(coolq.NewQQBot(cli))
|
||||||
log.Info("资源初始化完成, 开始处理信息.")
|
log.Info("资源初始化完成, 开始处理信息.")
|
||||||
log.Info("アトリは、高性能ですから!")
|
log.Info("アトリは、高性能ですから!")
|
||||||
|
@ -28,7 +28,7 @@ func (bot *CQBot) CQGetVersion() global.MSG {
|
|||||||
//
|
//
|
||||||
// @route12(send_message)
|
// @route12(send_message)
|
||||||
// @rename(m->message)
|
// @rename(m->message)
|
||||||
func (bot *CQBot) CQSendMessageV12(groupID, userID, detailType string, m gjson.Result) global.MSG {
|
func (bot *CQBot) CQSendMessageV12(groupID, userID, detailType string, m gjson.Result) global.MSG { // nolint
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
return OK(nil)
|
return OK(nil)
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
|
|||||||
bot.dispatch(gm)
|
bot.dispatch(gm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEvent) {
|
func (bot *CQBot) tempMessageEvent(_ *client.QQClient, e *client.TempMessageEvent) {
|
||||||
m := e.Message
|
m := e.Message
|
||||||
bot.checkMedia(m.Elements, m.Sender.Uin)
|
bot.checkMedia(m.Elements, m.Sender.Uin)
|
||||||
source := message.Source{
|
source := message.Source{
|
||||||
@ -491,7 +491,7 @@ func (bot *CQBot) leaveGroupEvent(c *client.QQClient, e *client.GroupLeaveEvent)
|
|||||||
bot.dispatch(bot.groupDecrease(e.Group.Code, c.Uin, e.Operator))
|
bot.dispatch(bot.groupDecrease(e.Group.Code, c.Uin, e.Operator))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *CQBot) memberPermissionChangedEvent(c *client.QQClient, e *client.MemberPermissionChangedEvent) {
|
func (bot *CQBot) memberPermissionChangedEvent(_ *client.QQClient, e *client.MemberPermissionChangedEvent) {
|
||||||
st := "unset"
|
st := "unset"
|
||||||
if e.NewPermission == client.Administrator {
|
if e.NewPermission == client.Administrator {
|
||||||
st = "set"
|
st = "set"
|
||||||
@ -502,7 +502,7 @@ func (bot *CQBot) memberPermissionChangedEvent(c *client.QQClient, e *client.Mem
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *CQBot) memberCardUpdatedEvent(c *client.QQClient, e *client.MemberCardUpdatedEvent) {
|
func (bot *CQBot) memberCardUpdatedEvent(_ *client.QQClient, e *client.MemberCardUpdatedEvent) {
|
||||||
log.Infof("群 %v 的 %v 更新了名片 %v -> %v", formatGroupName(e.Group), formatMemberName(e.Member), e.OldCard, e.Member.CardName)
|
log.Infof("群 %v 的 %v 更新了名片 %v -> %v", formatGroupName(e.Group), formatMemberName(e.Member), e.OldCard, e.Member.CardName)
|
||||||
bot.dispatchEvent("notice/group_card", global.MSG{
|
bot.dispatchEvent("notice/group_card", global.MSG{
|
||||||
"group_id": e.Group.Code,
|
"group_id": e.Group.Code,
|
||||||
@ -526,7 +526,7 @@ func (bot *CQBot) memberLeaveEvent(_ *client.QQClient, e *client.MemberLeaveGrou
|
|||||||
bot.dispatch(bot.groupDecrease(e.Group.Code, e.Member.Uin, e.Operator))
|
bot.dispatch(bot.groupDecrease(e.Group.Code, e.Member.Uin, e.Operator))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *CQBot) friendRequestEvent(c *client.QQClient, e *client.NewFriendRequest) {
|
func (bot *CQBot) friendRequestEvent(_ *client.QQClient, e *client.NewFriendRequest) {
|
||||||
log.Infof("收到来自 %v(%v) 的好友请求: %v", e.RequesterNick, e.RequesterUin, e.Message)
|
log.Infof("收到来自 %v(%v) 的好友请求: %v", e.RequesterNick, e.RequesterUin, e.Message)
|
||||||
flag := strconv.FormatInt(e.RequestId, 10)
|
flag := strconv.FormatInt(e.RequestId, 10)
|
||||||
bot.friendReqCache.Store(flag, e)
|
bot.friendReqCache.Store(flag, e)
|
||||||
@ -537,7 +537,7 @@ func (bot *CQBot) friendRequestEvent(c *client.QQClient, e *client.NewFriendRequ
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *CQBot) friendAddedEvent(c *client.QQClient, e *client.NewFriendEvent) {
|
func (bot *CQBot) friendAddedEvent(_ *client.QQClient, e *client.NewFriendEvent) {
|
||||||
log.Infof("添加了新好友: %v(%v)", e.Friend.Nickname, e.Friend.Uin)
|
log.Infof("添加了新好友: %v(%v)", e.Friend.Nickname, e.Friend.Uin)
|
||||||
bot.tempSessionCache.Delete(e.Friend.Uin)
|
bot.tempSessionCache.Delete(e.Friend.Uin)
|
||||||
bot.dispatchEvent("notice/friend_add", global.MSG{
|
bot.dispatchEvent("notice/friend_add", global.MSG{
|
||||||
@ -545,7 +545,7 @@ func (bot *CQBot) friendAddedEvent(c *client.QQClient, e *client.NewFriendEvent)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRequest) {
|
func (bot *CQBot) groupInvitedEvent(_ *client.QQClient, e *client.GroupInvitedRequest) {
|
||||||
log.Infof("收到来自群 %v(%v) 内用户 %v(%v) 的加群邀请.", e.GroupName, e.GroupCode, e.InvitorNick, e.InvitorUin)
|
log.Infof("收到来自群 %v(%v) 内用户 %v(%v) 的加群邀请.", e.GroupName, e.GroupCode, e.InvitorNick, e.InvitorUin)
|
||||||
flag := strconv.FormatInt(e.RequestId, 10)
|
flag := strconv.FormatInt(e.RequestId, 10)
|
||||||
bot.dispatchEvent("request/group/invite", global.MSG{
|
bot.dispatchEvent("request/group/invite", global.MSG{
|
||||||
@ -557,7 +557,7 @@ func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRe
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupRequest) {
|
func (bot *CQBot) groupJoinReqEvent(_ *client.QQClient, e *client.UserJoinGroupRequest) {
|
||||||
log.Infof("群 %v(%v) 收到来自用户 %v(%v) 的加群请求.", e.GroupName, e.GroupCode, e.RequesterNick, e.RequesterUin)
|
log.Infof("群 %v(%v) 收到来自用户 %v(%v) 的加群请求.", e.GroupName, e.GroupCode, e.RequesterNick, e.RequesterUin)
|
||||||
flag := strconv.FormatInt(e.RequestId, 10)
|
flag := strconv.FormatInt(e.RequestId, 10)
|
||||||
bot.dispatchEvent("request/group/add", global.MSG{
|
bot.dispatchEvent("request/group/add", global.MSG{
|
||||||
@ -569,7 +569,7 @@ func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupR
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *CQBot) otherClientStatusChangedEvent(c *client.QQClient, e *client.OtherClientStatusChangedEvent) {
|
func (bot *CQBot) otherClientStatusChangedEvent(_ *client.QQClient, e *client.OtherClientStatusChangedEvent) {
|
||||||
if e.Online {
|
if e.Online {
|
||||||
log.Infof("Bot 账号在客户端 %v (%v) 登录.", e.Client.DeviceName, e.Client.DeviceKind)
|
log.Infof("Bot 账号在客户端 %v (%v) 登录.", e.Client.DeviceName, e.Client.DeviceKind)
|
||||||
} else {
|
} else {
|
||||||
|
@ -39,6 +39,7 @@ var (
|
|||||||
AllowTempSession bool // 是否允许发送临时会话信息
|
AllowTempSession bool // 是否允许发送临时会话信息
|
||||||
UpdateProtocol bool // 是否更新协议
|
UpdateProtocol bool // 是否更新协议
|
||||||
SignServerOverwrite string // 使用特定的服务器进行签名
|
SignServerOverwrite string // 使用特定的服务器进行签名
|
||||||
|
HTTPTimeout int
|
||||||
|
|
||||||
PostFormat string // 上报格式 string or array
|
PostFormat string // 上报格式 string or array
|
||||||
Proxy string // 存储 proxy_rewrite,用于设置代理
|
Proxy string // 存储 proxy_rewrite,用于设置代理
|
||||||
@ -88,6 +89,7 @@ func Init() {
|
|||||||
ReportSelfMessage = conf.Message.ReportSelfMessage
|
ReportSelfMessage = conf.Message.ReportSelfMessage
|
||||||
UseSSOAddress = conf.Account.UseSSOAddress
|
UseSSOAddress = conf.Account.UseSSOAddress
|
||||||
AllowTempSession = conf.Account.AllowTempSession
|
AllowTempSession = conf.Account.AllowTempSession
|
||||||
|
HTTPTimeout = conf.Message.HTTPTimeout
|
||||||
}
|
}
|
||||||
{ // others
|
{ // others
|
||||||
Proxy = conf.Message.ProxyRewrite
|
Proxy = conf.Message.ProxyRewrite
|
||||||
|
@ -4,6 +4,7 @@ package download
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -22,18 +23,31 @@ import (
|
|||||||
|
|
||||||
var client = &http.Client{
|
var client = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
Proxy: func(request *http.Request) (u *url.URL, e error) {
|
Proxy: func(request *http.Request) (*url.URL, error) {
|
||||||
if base.Proxy == "" {
|
if base.Proxy == "" {
|
||||||
return http.ProxyFromEnvironment(request)
|
return http.ProxyFromEnvironment(request)
|
||||||
}
|
}
|
||||||
return url.Parse(base.Proxy)
|
return url.Parse(base.Proxy)
|
||||||
},
|
},
|
||||||
ForceAttemptHTTP2: false,
|
// Disable http2
|
||||||
MaxConnsPerHost: 0,
|
TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{},
|
||||||
MaxIdleConns: 0,
|
|
||||||
MaxIdleConnsPerHost: 999,
|
MaxIdleConnsPerHost: 999,
|
||||||
},
|
},
|
||||||
Timeout: time.Second * 15,
|
Timeout: time.Second * 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
var clienth2 = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: func(request *http.Request) (*url.URL, error) {
|
||||||
|
if base.Proxy == "" {
|
||||||
|
return http.ProxyFromEnvironment(request)
|
||||||
|
}
|
||||||
|
return url.Parse(base.Proxy)
|
||||||
|
},
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
MaxIdleConnsPerHost: 999,
|
||||||
|
},
|
||||||
|
Timeout: time.Second * 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrOverSize 响应主体过大时返回此错误
|
// ErrOverSize 响应主体过大时返回此错误
|
||||||
@ -42,6 +56,15 @@ var ErrOverSize = errors.New("oversize")
|
|||||||
// UserAgent HTTP请求时使用的UA
|
// UserAgent HTTP请求时使用的UA
|
||||||
const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
|
const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
|
||||||
|
|
||||||
|
// SetTimeout set internal/download client timeout
|
||||||
|
func SetTimeout(t time.Duration) {
|
||||||
|
if t == 0 {
|
||||||
|
t = time.Second * 10
|
||||||
|
}
|
||||||
|
client.Timeout = t
|
||||||
|
clienth2.Timeout = t
|
||||||
|
}
|
||||||
|
|
||||||
// Request is a file download request
|
// Request is a file download request
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Method string
|
Method string
|
||||||
@ -51,6 +74,13 @@ type Request struct {
|
|||||||
Body io.Reader
|
Body io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r Request) client() *http.Client {
|
||||||
|
if strings.Contains(r.URL, "go-cqhttp.org") {
|
||||||
|
return clienth2
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
func (r Request) do() (*http.Response, error) {
|
func (r Request) do() (*http.Response, error) {
|
||||||
if r.Method == "" {
|
if r.Method == "" {
|
||||||
r.Method = http.MethodGet
|
r.Method = http.MethodGet
|
||||||
@ -65,7 +95,7 @@ func (r Request) do() (*http.Response, error) {
|
|||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.Do(req)
|
return r.client().Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Request) body() (io.ReadCloser, error) {
|
func (r Request) body() (io.ReadCloser, error) {
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
//go:build amd64
|
|
||||||
|
|
||||||
package t544
|
|
||||||
|
|
||||||
func cpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
|
||||||
|
|
||||||
var canusesse2 = func() bool {
|
|
||||||
_, _, _, d := cpuid(1)
|
|
||||||
return d&(1<<26) > 0
|
|
||||||
}()
|
|
@ -1,15 +0,0 @@
|
|||||||
//go:build amd64
|
|
||||||
// +build amd64
|
|
||||||
|
|
||||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
|
||||||
|
|
||||||
// func cpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
|
||||||
TEXT ·cpuid(SB), 7, $0
|
|
||||||
XORQ CX, CX
|
|
||||||
MOVL op+0(FP), AX
|
|
||||||
CPUID
|
|
||||||
MOVL AX, eax+8(FP)
|
|
||||||
MOVL BX, ebx+12(FP)
|
|
||||||
MOVL CX, ecx+16(FP)
|
|
||||||
MOVL DX, edx+20(FP)
|
|
||||||
RET
|
|
@ -23,25 +23,36 @@ var crc32Table = func() (tab crc32.Table) {
|
|||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
func tencentCrc32(tab *crc32.Table, b []byte) uint32
|
func tencentCrc32(tab *crc32.Table, b []byte) uint32
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
func sub_a([]byte, []uint32)
|
func sub_a([]byte, []uint32)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
func sub_b([]byte, []uint32)
|
func sub_b([]byte, []uint32)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
func sub_c(*[16][16]byte, []byte)
|
func sub_c(*[16][16]byte, []byte)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
func sub_d(*[16]byte, []byte)
|
func sub_d(*[16]byte, []byte)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
func sub_e(*[256][6]byte, []byte)
|
func sub_e(*[256][6]byte, []byte)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
func sub_f(*[16]byte, *[15]uint32, *[16][16]byte) (w [44]uint32)
|
func sub_f(*[16]byte, *[15]uint32, *[16][16]byte) (w [44]uint32)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
func sub_aa(int, *[16][2][16][16]byte, *[16]byte, []byte) byte
|
func sub_aa(int, *[16][2][16][16]byte, *[16]byte, []byte) byte
|
||||||
|
|
||||||
// transformInner see com/tencent/mobileqq/dt/model/FEBound
|
// transformInner see com/tencent/mobileqq/dt/model/FEBound
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
func transformInner(*[0x15]byte, *[32][16]byte)
|
func transformInner(*[0x15]byte, *[32][16]byte)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
func initState(*state, []byte, []byte, uint64)
|
func initState(*state, []byte, []byte, uint64)
|
||||||
|
|
||||||
func (c *state) init(key []byte, data []byte, counter uint64, nr uint8) {
|
func (c *state) init(key []byte, data []byte, counter uint64, nr uint8) {
|
||||||
@ -50,4 +61,5 @@ func (c *state) init(key []byte, data []byte, counter uint64, nr uint8) {
|
|||||||
initState(c, key, data, counter)
|
initState(c, key, data, counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sub_ad([]uint32)
|
//go:noescape
|
||||||
|
func refreshState(c *state)
|
||||||
|
@ -10,8 +10,8 @@ DATA LC0<>+12(SB)/4, $1797285236
|
|||||||
GLOBL LC0<>(SB), NOPTR, $16
|
GLOBL LC0<>(SB), NOPTR, $16
|
||||||
|
|
||||||
TEXT ·sub_a(SB), NOSPLIT, $0-48
|
TEXT ·sub_a(SB), NOSPLIT, $0-48
|
||||||
MOVQ ·a+0(FP), DI
|
MOVQ a+0(FP), DI
|
||||||
MOVQ ·b+24(FP), CX
|
MOVQ b+24(FP), CX
|
||||||
MOVQ CX, DX
|
MOVQ CX, DX
|
||||||
MOVBLZX 3(CX), CX
|
MOVBLZX 3(CX), CX
|
||||||
XORB CX, (DI)
|
XORB CX, (DI)
|
||||||
@ -48,8 +48,8 @@ TEXT ·sub_a(SB), NOSPLIT, $0-48
|
|||||||
RET
|
RET
|
||||||
|
|
||||||
TEXT ·sub_b(SB), NOSPLIT, $0-48
|
TEXT ·sub_b(SB), NOSPLIT, $0-48
|
||||||
MOVQ ·a+0(FP), DI
|
MOVQ a+0(FP), DI
|
||||||
MOVQ ·b+24(FP), CX
|
MOVQ b+24(FP), CX
|
||||||
MOVQ CX, DX
|
MOVQ CX, DX
|
||||||
MOVBLZX 3(CX), CX
|
MOVBLZX 3(CX), CX
|
||||||
XORB CX, (DI)
|
XORB CX, (DI)
|
||||||
@ -87,8 +87,8 @@ TEXT ·sub_b(SB), NOSPLIT, $0-48
|
|||||||
|
|
||||||
|
|
||||||
TEXT ·sub_c(SB), NOSPLIT, $0-32
|
TEXT ·sub_c(SB), NOSPLIT, $0-32
|
||||||
MOVQ ·a+0(FP), DI
|
MOVQ a+0(FP), DI
|
||||||
MOVQ ·b+8(FP), SI
|
MOVQ b+8(FP), SI
|
||||||
MOVQ SI, AX
|
MOVQ SI, AX
|
||||||
MOVBLZX (SI), SI
|
MOVBLZX (SI), SI
|
||||||
MOVL SI, CX
|
MOVL SI, CX
|
||||||
@ -236,26 +236,27 @@ TEXT ·sub_c(SB), NOSPLIT, $0-32
|
|||||||
MOVB CX, 15(AX)
|
MOVB CX, 15(AX)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
TEXT ·sub_d(SB), NOSPLIT, $16-32
|
TEXT ·sub_d(SB), NOSPLIT, $24-32
|
||||||
MOVQ ·t+0(FP), BX
|
MOVQ t+0(FP), BX
|
||||||
MOVQ ·s+8(FP), SI
|
MOVQ s+8(FP), DI
|
||||||
MOVOU (SI), X0
|
MOVOU (DI), X0
|
||||||
MOVOU X0, in-16(SP)
|
MOVOU X0, in-16(SP)
|
||||||
MOVQ SI, DI
|
MOVQ $16, CX
|
||||||
ADDQ $15, DI
|
ADDQ $15, DI
|
||||||
MOVB $16, CX
|
PUSHFQ
|
||||||
|
STD
|
||||||
lop:
|
lop:
|
||||||
LEAQ -1(CX), AX
|
LEAQ -1(CX), AX
|
||||||
XLAT
|
XLAT
|
||||||
MOVBLZX in-16(SP)(AX*1), AX
|
LEAQ in-16(SP)(AX*1), SI
|
||||||
STD
|
MOVSB
|
||||||
STOSB
|
|
||||||
LOOP lop
|
LOOP lop
|
||||||
|
POPFQ
|
||||||
RET
|
RET
|
||||||
|
|
||||||
TEXT ·sub_e(SB), NOSPLIT, $0-32
|
TEXT ·sub_e(SB), NOSPLIT, $0-32
|
||||||
MOVQ ·a+0(FP), DI
|
MOVQ a+0(FP), DI
|
||||||
MOVQ ·n+8(FP), SI
|
MOVQ n+8(FP), SI
|
||||||
MOVQ $4, AX
|
MOVQ $4, AX
|
||||||
lop:
|
lop:
|
||||||
MOVBQZX -4(SI)(AX*4), DX
|
MOVBQZX -4(SI)(AX*4), DX
|
||||||
@ -295,9 +296,9 @@ lop:
|
|||||||
JNZ lop
|
JNZ lop
|
||||||
RET
|
RET
|
||||||
|
|
||||||
TEXT sub_ab(SB), NOSPLIT, $0-24
|
TEXT sub_ab<>(SB), NOSPLIT, $0-24
|
||||||
MOVQ ·s+0(FP), DI
|
MOVQ s+0(FP), DI
|
||||||
MOVQ ·w+8(FP), SI
|
MOVQ w+8(FP), SI
|
||||||
MOVL SI, AX
|
MOVL SI, AX
|
||||||
MOVL SI, CX
|
MOVL SI, CX
|
||||||
MOVL SI, DX
|
MOVL SI, DX
|
||||||
@ -329,14 +330,14 @@ TEXT sub_ab(SB), NOSPLIT, $0-24
|
|||||||
MOVBLZX (DI)(DX*1), DX
|
MOVBLZX (DI)(DX*1), DX
|
||||||
SALL $16, DX
|
SALL $16, DX
|
||||||
ORL DX, AX
|
ORL DX, AX
|
||||||
MOVQ AX, ·retval+16(FP)
|
MOVQ AX, retval+16(FP)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
TEXT ·sub_f(SB), NOSPLIT, $24-68
|
TEXT ·sub_f(SB), NOSPLIT, $24-68
|
||||||
MOVQ ·k+0(FP), DI
|
MOVQ k+0(FP), DI
|
||||||
MOVQ ·r+8(FP), SI
|
MOVQ r+8(FP), SI
|
||||||
MOVQ ·s+16(FP), DX
|
MOVQ s+16(FP), DX
|
||||||
MOVQ $·w+24(FP), CX
|
MOVQ $w+24(FP), CX
|
||||||
MOVQ CX, R10
|
MOVQ CX, R10
|
||||||
MOVQ SI, R9
|
MOVQ SI, R9
|
||||||
MOVQ DX, R8
|
MOVQ DX, R8
|
||||||
@ -367,7 +368,7 @@ inner:
|
|||||||
ROLL $8, AX
|
ROLL $8, AX
|
||||||
MOVQ R8, 0(SP)
|
MOVQ R8, 0(SP)
|
||||||
MOVL AX, 8(SP)
|
MOVL AX, 8(SP)
|
||||||
CALL sub_ab(SB)
|
CALL sub_ab<>(SB)
|
||||||
MOVQ 16(SP), AX
|
MOVQ 16(SP), AX
|
||||||
LEAL -1(BX), DX
|
LEAL -1(BX), DX
|
||||||
SARL $2, DX
|
SARL $2, DX
|
||||||
@ -378,10 +379,10 @@ end:
|
|||||||
RET
|
RET
|
||||||
|
|
||||||
TEXT ·sub_aa(SB), NOSPLIT, $0-56
|
TEXT ·sub_aa(SB), NOSPLIT, $0-56
|
||||||
MOVQ ·i+0(FP), DI
|
MOVQ i+0(FP), DI
|
||||||
MOVQ ·t+8(FP), SI
|
MOVQ t+8(FP), SI
|
||||||
MOVQ ·b+16(FP), DX
|
MOVQ b+16(FP), DX
|
||||||
MOVQ ·m+24(FP), CX
|
MOVQ m+24(FP), CX
|
||||||
MOVL DI, AX
|
MOVL DI, AX
|
||||||
MOVLQSX DI, DI
|
MOVLQSX DI, DI
|
||||||
MOVQ SI, R8
|
MOVQ SI, R8
|
||||||
@ -407,13 +408,13 @@ TEXT ·sub_aa(SB), NOSPLIT, $0-56
|
|||||||
MOVBLZX (AX)(DI*1), AX
|
MOVBLZX (AX)(DI*1), AX
|
||||||
SALL $4, AX
|
SALL $4, AX
|
||||||
ORB 256(SI)(DX*1), AX
|
ORB 256(SI)(DX*1), AX
|
||||||
MOVQ AX, ·retval+48(FP)
|
MOVQ AX, retval+48(FP)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
// func transformInner(x *[0x15]byte, tab *[32][16]byte)
|
// func transformInner(x *[0x15]byte, tab *[32][16]byte)
|
||||||
TEXT ·transformInner(SB), NOSPLIT, $0-16
|
TEXT ·transformInner(SB), NOSPLIT, $0-16
|
||||||
MOVQ ·x+0(FP), DI
|
MOVQ x+0(FP), DI
|
||||||
MOVQ ·tab+8(FP), SI
|
MOVQ tab+8(FP), SI
|
||||||
MOVQ DI, AX
|
MOVQ DI, AX
|
||||||
MOVL $1, CX
|
MOVL $1, CX
|
||||||
MOVQ SI, DI
|
MOVQ SI, DI
|
||||||
@ -446,10 +447,10 @@ lop:
|
|||||||
RET
|
RET
|
||||||
|
|
||||||
TEXT ·initState(SB), NOSPLIT, $0-64
|
TEXT ·initState(SB), NOSPLIT, $0-64
|
||||||
MOVQ ·c+0(FP), DI
|
MOVQ c+0(FP), DI
|
||||||
MOVQ ·key+8(FP), SI
|
MOVQ key+8(FP), SI
|
||||||
MOVQ ·data+32(FP), R8
|
MOVQ data+32(FP), R8
|
||||||
MOVQ ·counter+56(FP), AX
|
MOVQ counter+56(FP), AX
|
||||||
MOVOA LC0<>(SB), X0
|
MOVOA LC0<>(SB), X0
|
||||||
MOVUPS X0, (DI)
|
MOVUPS X0, (DI)
|
||||||
MOVOU (SI), X1
|
MOVOU (SI), X1
|
||||||
@ -467,8 +468,8 @@ TEXT ·initState(SB), NOSPLIT, $0-64
|
|||||||
MOVUPS X6,112(DI)
|
MOVUPS X6,112(DI)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
TEXT ·sub_ad(SB), NOSPLIT, $8-24
|
TEXT sub_ad<>(SB), NOSPLIT, $8-8
|
||||||
MOVQ ·a+0(FP), DI
|
MOVQ a+0(FP), DI
|
||||||
MOVQ DI, AX
|
MOVQ DI, AX
|
||||||
MOVL 40(DI), R10
|
MOVL 40(DI), R10
|
||||||
MOVL 12(DI), R12
|
MOVL 12(DI), R12
|
||||||
@ -610,11 +611,42 @@ TEXT ·sub_ad(SB), NOSPLIT, $8-24
|
|||||||
MOVUPS X0, 32(AX)
|
MOVUPS X0, 32(AX)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
|
TEXT ·refreshState(SB), NOSPLIT, $16-8
|
||||||
|
MOVQ i+0(FP), BX
|
||||||
|
MOVB 128(BX), CX
|
||||||
|
JE ad
|
||||||
|
SHRQ $1, CX
|
||||||
|
fr:
|
||||||
|
MOVQ BX, 0(SP)
|
||||||
|
MOVQ CX, c-8(SP)
|
||||||
|
CALL sub_ad<>(SB)
|
||||||
|
MOVQ c-8(SP), CX
|
||||||
|
MOVQ i+0(FP), BX
|
||||||
|
LOOP fr
|
||||||
|
ad:
|
||||||
|
MOVOU (BX), X0
|
||||||
|
MOVOU 64(BX), X1
|
||||||
|
MOVOU 80(BX), X2
|
||||||
|
MOVOU 96(BX), X3
|
||||||
|
PADDD X1, X0
|
||||||
|
MOVOU 48(BX), X4
|
||||||
|
MOVUPS X0, (BX)
|
||||||
|
MOVOU 16(BX), X0
|
||||||
|
PADDD X2, X0
|
||||||
|
MOVUPS X0, 16(BX)
|
||||||
|
MOVOU 32(BX), X0
|
||||||
|
PADDD X3, X0
|
||||||
|
MOVUPS X0, 32(BX)
|
||||||
|
MOVOU 112(BX), X0
|
||||||
|
PADDD X4, X0
|
||||||
|
MOVUPS X0, 48(BX)
|
||||||
|
RET
|
||||||
|
|
||||||
// func tencentCrc32(tab *crc32.Table, b []byte) uint32
|
// func tencentCrc32(tab *crc32.Table, b []byte) uint32
|
||||||
TEXT ·tencentCrc32(SB), NOSPLIT, $0-40
|
TEXT ·tencentCrc32(SB), NOSPLIT, $0-40
|
||||||
MOVQ ·tab+0(FP), DI
|
MOVQ tab+0(FP), DI
|
||||||
MOVQ ·bptr+8(FP), SI
|
MOVQ bptr+8(FP), SI
|
||||||
MOVQ ·bngas+16(FP), DX
|
MOVQ bngas+16(FP), DX
|
||||||
TESTQ DX, DX
|
TESTQ DX, DX
|
||||||
JE quickend
|
JE quickend
|
||||||
ADDQ SI, DX
|
ADDQ SI, DX
|
||||||
@ -629,7 +661,7 @@ lop:
|
|||||||
CMPQ SI, DX
|
CMPQ SI, DX
|
||||||
JNE lop
|
JNE lop
|
||||||
NOTL AX
|
NOTL AX
|
||||||
MOVQ AX, ·bngas+32(FP)
|
MOVQ AX, bngas+32(FP)
|
||||||
RET
|
RET
|
||||||
quickend:
|
quickend:
|
||||||
XORL AX, AX
|
XORL AX, AX
|
||||||
|
@ -91,12 +91,7 @@ func (c *state) encrypt(data []byte) {
|
|||||||
dataLen := uint32(len(data))
|
dataLen := uint32(len(data))
|
||||||
for dataLen > 0 {
|
for dataLen > 0 {
|
||||||
if c.p == 0 {
|
if c.p == 0 {
|
||||||
for i := uint8(0); i < c.nr; i += 2 {
|
refreshState(c)
|
||||||
sub_ad(c.state[:])
|
|
||||||
}
|
|
||||||
for i := 0; i < 16; i++ {
|
|
||||||
c.state[i] += c.orgstate[i]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var sb [16 * 4]byte
|
var sb [16 * 4]byte
|
||||||
for i, v := range c.state {
|
for i, v := range c.state {
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"crypto/rc4"
|
"crypto/rc4"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/encryption"
|
"github.com/Mrs4s/go-cqhttp/internal/encryption"
|
||||||
)
|
)
|
||||||
@ -24,17 +23,17 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if canusesse2 {
|
encryption.T544Signer["8.9.35.10440"] = sign
|
||||||
encryption.T544Signer["8.9.35.10440"] = sign
|
encryption.T544Signer["8.9.38.10545"] = sign
|
||||||
encryption.T544Signer["8.9.38.10545"] = sign
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign t544 algorithm
|
// sign t544 algorithm
|
||||||
// special thanks to the anonymous contributor who provided the algorithm
|
// special thanks to the anonymous contributor who provided the algorithm
|
||||||
func sign(curr int64, input []byte) []byte {
|
func sign(curr int64, input []byte) []byte {
|
||||||
|
var crcData [0x15]byte
|
||||||
curr %= 1000000
|
curr %= 1000000
|
||||||
input = append(input, []byte{byte(curr >> 24), byte(curr >> 16), byte(curr >> 8), byte(curr)}...)
|
binary.BigEndian.PutUint32(crcData[:4], uint32(curr))
|
||||||
|
input = append(input, crcData[:4]...)
|
||||||
var kt [4 + 32 + 4]byte
|
var kt [4 + 32 + 4]byte
|
||||||
r := rand.New(rand.NewSource(curr))
|
r := rand.New(rand.NewSource(curr))
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
@ -53,8 +52,7 @@ func sign(curr int64, input []byte) []byte {
|
|||||||
k3calc[6], k3calc[7] = 0, 0
|
k3calc[6], k3calc[7] = 0, 0
|
||||||
rc4Cipher, _ := rc4.NewCipher(key3)
|
rc4Cipher, _ := rc4.NewCipher(key3)
|
||||||
rc4Cipher.XORKeyStream(key3, key3)
|
rc4Cipher.XORKeyStream(key3, key3)
|
||||||
var crcData [0x15]byte
|
binary.LittleEndian.PutUint64(crcData[4:4+8], magic)
|
||||||
copy(crcData[4:4+8], (*[8]byte)(unsafe.Pointer(&magic))[:])
|
|
||||||
tencentEncryptionA(input, kt[4:4+32], crcData[4:4+8])
|
tencentEncryptionA(input, kt[4:4+32], crcData[4:4+8])
|
||||||
result := md5.Sum(input)
|
result := md5.Sum(input)
|
||||||
crcData[2] = 1
|
crcData[2] = 1
|
||||||
@ -63,7 +61,7 @@ func sign(curr int64, input []byte) []byte {
|
|||||||
binary.BigEndian.PutUint32(crcData[9:13], uint32(curr))
|
binary.BigEndian.PutUint32(crcData[9:13], uint32(curr))
|
||||||
copy(crcData[13:], result[:8])
|
copy(crcData[13:], result[:8])
|
||||||
calcCrc := tencentCrc32(&crc32Table, crcData[2:])
|
calcCrc := tencentCrc32(&crc32Table, crcData[2:])
|
||||||
copy(kt[4+32:4+32+4], (*[4]byte)(unsafe.Pointer(&calcCrc))[:])
|
binary.LittleEndian.PutUint32(kt[4+32:4+32+4], calcCrc)
|
||||||
crcData[0] = kt[4+32]
|
crcData[0] = kt[4+32]
|
||||||
crcData[1] = kt[4+32+3]
|
crcData[1] = kt[4+32+3]
|
||||||
nonce := uint32(r.Int() ^ r.Int() ^ r.Int())
|
nonce := uint32(r.Int() ^ r.Int() ^ r.Int())
|
||||||
|
22
internal/encryption/t544/t544_test.go
Executable file
22
internal/encryption/t544/t544_test.go
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
package t544
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestT544(t *testing.T) {
|
||||||
|
r := hex.EncodeToString(sign(0, []byte{}))
|
||||||
|
if r != "0c05d28b405bce1595c70ffa694ff163d4b600f229482e07de32c8000000003525382c00000000" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCrash(t *testing.T) {
|
||||||
|
brand := make([]byte, 4096)
|
||||||
|
for i := 1; i <= 1024; i++ {
|
||||||
|
rand.Reader.Read(brand)
|
||||||
|
sign(123, brand)
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseString(t *testing.T) {
|
func TestParseString(_ *testing.T) {
|
||||||
// TODO: add more text
|
// TODO: add more text
|
||||||
for _, v := range ParseString(`[CQ:face,id=115,text=111][CQ:face,id=217]] [CQ:text,text=123] [`) {
|
for _, v := range ParseString(`[CQ:face,id=115,text=111][CQ:face,id=217]] [CQ:text,text=123] [`) {
|
||||||
fmt.Println(v)
|
fmt.Println(v)
|
||||||
|
@ -56,6 +56,7 @@ type Config struct {
|
|||||||
ExtraReplyData bool `yaml:"extra-reply-data"`
|
ExtraReplyData bool `yaml:"extra-reply-data"`
|
||||||
SkipMimeScan bool `yaml:"skip-mime-scan"`
|
SkipMimeScan bool `yaml:"skip-mime-scan"`
|
||||||
ConvertWebpImage bool `yaml:"convert-webp-image"`
|
ConvertWebpImage bool `yaml:"convert-webp-image"`
|
||||||
|
HTTPTimeout int `yaml:"http-timeout"`
|
||||||
} `yaml:"message"`
|
} `yaml:"message"`
|
||||||
|
|
||||||
Output struct {
|
Output struct {
|
||||||
|
78
pkg/onebot/attr.go
Normal file
78
pkg/onebot/attr.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package onebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Attr is a key-value pair.
|
||||||
|
type Attr struct {
|
||||||
|
Key string
|
||||||
|
Value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns an Attr for a string value.
|
||||||
|
func String(key, value string) Attr {
|
||||||
|
return Attr{key, StringValue(value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns an Attr for an int64.
|
||||||
|
func Int64(key string, value int64) Attr {
|
||||||
|
return Attr{key, Int64Value(value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int converts an int to an int64 and returns
|
||||||
|
// an Attr with that value.
|
||||||
|
func Int(key string, value int) Attr {
|
||||||
|
return Int64(key, int64(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 returns an Attr for a uint64.
|
||||||
|
func Uint64(key string, v uint64) Attr {
|
||||||
|
return Attr{key, Uint64Value(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 returns an Attr for a floating-point number.
|
||||||
|
func Float64(key string, v float64) Attr {
|
||||||
|
return Attr{key, Float64Value(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns an Attr for a bool.
|
||||||
|
func Bool(key string, v bool) Attr {
|
||||||
|
return Attr{key, BoolValue(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns an Attr for a time.Time.
|
||||||
|
// It discards the monotonic portion.
|
||||||
|
func Time(key string, v time.Time) Attr {
|
||||||
|
return Attr{key, TimeValue(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration returns an Attr for a time.Duration.
|
||||||
|
func Duration(key string, v time.Duration) Attr {
|
||||||
|
return Attr{key, DurationValue(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group returns an Attr for a Group Value.
|
||||||
|
// The caller must not subsequently mutate the
|
||||||
|
// argument slice.
|
||||||
|
//
|
||||||
|
// Use Group to collect several Attrs under a single
|
||||||
|
// key on a log line, or as the result of LogValue
|
||||||
|
// in order to log a single value as multiple Attrs.
|
||||||
|
func Group(key string, as ...Attr) Attr {
|
||||||
|
return Attr{key, GroupValue(as...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any returns an Attr for the supplied value.
|
||||||
|
// See [Value.AnyValue] for how values are treated.
|
||||||
|
func Any(key string, value any) Attr {
|
||||||
|
return Attr{key, AnyValue(value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Attr) String() string {
|
||||||
|
return a.Key + "=" + a.Value.String()
|
||||||
|
}
|
31
pkg/onebot/kind_string.go
Normal file
31
pkg/onebot/kind_string.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Code generated by "stringer -type=Kind -trimprefix=Kind"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package onebot
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[KindAny-0]
|
||||||
|
_ = x[KindBool-1]
|
||||||
|
_ = x[KindDuration-2]
|
||||||
|
_ = x[KindFloat64-3]
|
||||||
|
_ = x[KindInt64-4]
|
||||||
|
_ = x[KindString-5]
|
||||||
|
_ = x[KindTime-6]
|
||||||
|
_ = x[KindUint64-7]
|
||||||
|
_ = x[KindGroup-8]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Kind_name = "AnyBoolDurationFloat64Int64StringTimeUint64Group"
|
||||||
|
|
||||||
|
var _Kind_index = [...]uint8{0, 3, 7, 15, 22, 27, 33, 37, 43, 48}
|
||||||
|
|
||||||
|
func (i Kind) String() string {
|
||||||
|
if i < 0 || i >= Kind(len(_Kind_index)-1) {
|
||||||
|
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
|
||||||
|
}
|
355
pkg/onebot/value.go
Normal file
355
pkg/onebot/value.go
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package onebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Value can represent any Go value, but unlike type any,
|
||||||
|
// it can represent most small values without an allocation.
|
||||||
|
// The zero Value corresponds to nil.
|
||||||
|
type Value struct {
|
||||||
|
_ [0]func() // disallow ==
|
||||||
|
num uint64 // hold number value
|
||||||
|
any any // hold Kind or other value
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
stringptr *byte // used in Value.any when the Value is a string
|
||||||
|
groupptr *Attr // used in Value.any when the Value is a []Attr
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate stringer -type=Kind -trimprefix=Kind
|
||||||
|
|
||||||
|
// Kind is the kind of Value.
|
||||||
|
type Kind int
|
||||||
|
|
||||||
|
// Kind
|
||||||
|
const (
|
||||||
|
KindAny Kind = iota
|
||||||
|
KindBool
|
||||||
|
KindDuration
|
||||||
|
KindFloat64
|
||||||
|
KindInt64
|
||||||
|
KindString
|
||||||
|
KindTime
|
||||||
|
KindUint64
|
||||||
|
KindGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unexported version of Kind, just so we can store Kinds in Values.
|
||||||
|
// (No user-provided value has this type.)
|
||||||
|
type kind Kind
|
||||||
|
|
||||||
|
// Kind returns v's Kind.
|
||||||
|
func (v Value) Kind() Kind {
|
||||||
|
switch x := v.any.(type) {
|
||||||
|
case Kind:
|
||||||
|
return x
|
||||||
|
case stringptr:
|
||||||
|
return KindString
|
||||||
|
case timeLocation:
|
||||||
|
return KindTime
|
||||||
|
case groupptr:
|
||||||
|
return KindGroup
|
||||||
|
case kind: // a kind is just a wrapper for a Kind
|
||||||
|
return KindAny
|
||||||
|
default:
|
||||||
|
return KindAny
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////// Constructors
|
||||||
|
|
||||||
|
// StringValue returns a new Value for a string.
|
||||||
|
func StringValue(value string) Value {
|
||||||
|
return Value{num: uint64(len(value)), any: stringptr(unsafe.StringData(value))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntValue returns a Value for an int.
|
||||||
|
func IntValue(v int) Value {
|
||||||
|
return Int64Value(int64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Value returns a Value for an int64.
|
||||||
|
func Int64Value(v int64) Value {
|
||||||
|
return Value{num: uint64(v), any: KindInt64}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64Value returns a Value for a uint64.
|
||||||
|
func Uint64Value(v uint64) Value {
|
||||||
|
return Value{num: v, any: KindUint64}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Value returns a Value for a floating-point number.
|
||||||
|
func Float64Value(v float64) Value {
|
||||||
|
return Value{num: math.Float64bits(v), any: KindFloat64}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolValue returns a Value for a bool.
|
||||||
|
func BoolValue(v bool) Value {
|
||||||
|
u := uint64(0)
|
||||||
|
if v {
|
||||||
|
u = 1
|
||||||
|
}
|
||||||
|
return Value{num: u, any: KindBool}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unexported version of *time.Location, just so we can store *time.Locations in
|
||||||
|
// Values. (No user-provided value has this type.)
|
||||||
|
type timeLocation *time.Location
|
||||||
|
|
||||||
|
// TimeValue returns a Value for a time.Time.
|
||||||
|
// It discards the monotonic portion.
|
||||||
|
func TimeValue(v time.Time) Value {
|
||||||
|
if v.IsZero() {
|
||||||
|
// UnixNano on the zero time is undefined, so represent the zero time
|
||||||
|
// with a nil *time.Location instead. time.Time.Location method never
|
||||||
|
// returns nil, so a Value with any == timeLocation(nil) cannot be
|
||||||
|
// mistaken for any other Value, time.Time or otherwise.
|
||||||
|
return Value{any: timeLocation(nil)}
|
||||||
|
}
|
||||||
|
return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DurationValue returns a Value for a time.Duration.
|
||||||
|
func DurationValue(v time.Duration) Value {
|
||||||
|
return Value{num: uint64(v.Nanoseconds()), any: KindDuration}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupValue returns a new Value for a list of Attrs.
|
||||||
|
// The caller must not subsequently mutate the argument slice.
|
||||||
|
func GroupValue(as ...Attr) Value {
|
||||||
|
return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyValue returns a Value for the supplied value.
|
||||||
|
//
|
||||||
|
// If the supplied value is of type Value, it is returned
|
||||||
|
// unmodified.
|
||||||
|
//
|
||||||
|
// Given a value of one of Go's predeclared string, bool, or
|
||||||
|
// (non-complex) numeric types, AnyValue returns a Value of kind
|
||||||
|
// String, Bool, Uint64, Int64, or Float64. The width of the
|
||||||
|
// original numeric type is not preserved.
|
||||||
|
//
|
||||||
|
// Given a time.Time or time.Duration value, AnyValue returns a Value of kind
|
||||||
|
// KindTime or KindDuration. The monotonic time is not preserved.
|
||||||
|
//
|
||||||
|
// For nil, or values of all other types, including named types whose
|
||||||
|
// underlying type is numeric, AnyValue returns a value of kind KindAny.
|
||||||
|
func AnyValue(v any) Value {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
return StringValue(v)
|
||||||
|
case int:
|
||||||
|
return Int64Value(int64(v))
|
||||||
|
case uint:
|
||||||
|
return Uint64Value(uint64(v))
|
||||||
|
case int64:
|
||||||
|
return Int64Value(v)
|
||||||
|
case uint64:
|
||||||
|
return Uint64Value(v)
|
||||||
|
case bool:
|
||||||
|
return BoolValue(v)
|
||||||
|
case time.Duration:
|
||||||
|
return DurationValue(v)
|
||||||
|
case time.Time:
|
||||||
|
return TimeValue(v)
|
||||||
|
case uint8:
|
||||||
|
return Uint64Value(uint64(v))
|
||||||
|
case uint16:
|
||||||
|
return Uint64Value(uint64(v))
|
||||||
|
case uint32:
|
||||||
|
return Uint64Value(uint64(v))
|
||||||
|
case uintptr:
|
||||||
|
return Uint64Value(uint64(v))
|
||||||
|
case int8:
|
||||||
|
return Int64Value(int64(v))
|
||||||
|
case int16:
|
||||||
|
return Int64Value(int64(v))
|
||||||
|
case int32:
|
||||||
|
return Int64Value(int64(v))
|
||||||
|
case float64:
|
||||||
|
return Float64Value(v)
|
||||||
|
case float32:
|
||||||
|
return Float64Value(float64(v))
|
||||||
|
case []Attr:
|
||||||
|
return GroupValue(v...)
|
||||||
|
case Kind:
|
||||||
|
return Value{any: kind(v)}
|
||||||
|
case Value:
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return Value{any: v}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////// Accessors
|
||||||
|
|
||||||
|
// Any returns v's value as an any.
|
||||||
|
func (v Value) Any() any {
|
||||||
|
switch v.Kind() {
|
||||||
|
case KindAny:
|
||||||
|
if k, ok := v.any.(kind); ok {
|
||||||
|
return Kind(k)
|
||||||
|
}
|
||||||
|
return v.any
|
||||||
|
case KindGroup:
|
||||||
|
return v.group()
|
||||||
|
case KindInt64:
|
||||||
|
return int64(v.num)
|
||||||
|
case KindUint64:
|
||||||
|
return v.num
|
||||||
|
case KindFloat64:
|
||||||
|
return v.float()
|
||||||
|
case KindString:
|
||||||
|
return v.str()
|
||||||
|
case KindBool:
|
||||||
|
return v.bool()
|
||||||
|
case KindDuration:
|
||||||
|
return v.duration()
|
||||||
|
case KindTime:
|
||||||
|
return v.time()
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
|
||||||
|
// the methods Int64, Float64, and so on, which panic if v is of the
|
||||||
|
// wrong kind, String never panics.
|
||||||
|
func (v Value) String() string {
|
||||||
|
if sp, ok := v.any.(stringptr); ok {
|
||||||
|
return unsafe.String(sp, v.num)
|
||||||
|
}
|
||||||
|
var buf []byte
|
||||||
|
return string(v.append(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) str() string {
|
||||||
|
return unsafe.String(v.any.(stringptr), v.num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns v's value as an int64. It panics
|
||||||
|
// if v is not a signed integer.
|
||||||
|
func (v Value) Int64() int64 {
|
||||||
|
if g, w := v.Kind(), KindInt64; g != w {
|
||||||
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||||
|
}
|
||||||
|
return int64(v.num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 returns v's value as a uint64. It panics
|
||||||
|
// if v is not an unsigned integer.
|
||||||
|
func (v Value) Uint64() uint64 {
|
||||||
|
if g, w := v.Kind(), KindUint64; g != w {
|
||||||
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||||
|
}
|
||||||
|
return v.num
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns v's value as a bool. It panics
|
||||||
|
// if v is not a bool.
|
||||||
|
func (v Value) Bool() bool {
|
||||||
|
if g, w := v.Kind(), KindBool; g != w {
|
||||||
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||||
|
}
|
||||||
|
return v.bool()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) bool() bool {
|
||||||
|
return v.num == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration returns v's value as a time.Duration. It panics
|
||||||
|
// if v is not a time.Duration.
|
||||||
|
func (v Value) Duration() time.Duration {
|
||||||
|
if g, w := v.Kind(), KindDuration; g != w {
|
||||||
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.duration()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) duration() time.Duration {
|
||||||
|
return time.Duration(int64(v.num))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 returns v's value as a float64. It panics
|
||||||
|
// if v is not a float64.
|
||||||
|
func (v Value) Float64() float64 {
|
||||||
|
if g, w := v.Kind(), KindFloat64; g != w {
|
||||||
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.float()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) float() float64 {
|
||||||
|
return math.Float64frombits(v.num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns v's value as a time.Time. It panics
|
||||||
|
// if v is not a time.Time.
|
||||||
|
func (v Value) Time() time.Time {
|
||||||
|
if g, w := v.Kind(), KindTime; g != w {
|
||||||
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||||
|
}
|
||||||
|
return v.time()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) time() time.Time {
|
||||||
|
loc := v.any.(timeLocation)
|
||||||
|
if loc == nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return time.Unix(0, int64(v.num)).In(loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group returns v's value as a []Attr.
|
||||||
|
// It panics if v's Kind is not KindGroup.
|
||||||
|
func (v Value) Group() []Attr {
|
||||||
|
if sp, ok := v.any.(groupptr); ok {
|
||||||
|
return unsafe.Slice(sp, v.num)
|
||||||
|
}
|
||||||
|
panic("Group: bad kind")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) group() []Attr {
|
||||||
|
return unsafe.Slice((*Attr)(v.any.(groupptr)), v.num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// append appends a text representation of v to dst.
|
||||||
|
// v is formatted as with fmt.Sprint.
|
||||||
|
func (v Value) append(dst []byte) []byte {
|
||||||
|
switch v.Kind() {
|
||||||
|
case KindString:
|
||||||
|
return append(dst, v.str()...)
|
||||||
|
case KindInt64:
|
||||||
|
return strconv.AppendInt(dst, int64(v.num), 10)
|
||||||
|
case KindUint64:
|
||||||
|
return strconv.AppendUint(dst, v.num, 10)
|
||||||
|
case KindFloat64:
|
||||||
|
return strconv.AppendFloat(dst, v.float(), 'g', -1, 64)
|
||||||
|
case KindBool:
|
||||||
|
return strconv.AppendBool(dst, v.bool())
|
||||||
|
case KindDuration:
|
||||||
|
return append(dst, v.duration().String()...)
|
||||||
|
case KindTime:
|
||||||
|
return append(dst, v.time().String()...)
|
||||||
|
case KindGroup:
|
||||||
|
return fmt.Append(dst, v.group())
|
||||||
|
case KindAny:
|
||||||
|
return fmt.Append(dst, v.any)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user