Skip to content

Commit

Permalink
feat(QQ): 实现内置 lagrange 的原型版本 (#668)
Browse files Browse the repository at this point in the history
Signed-off-by: JustAnotherID <[email protected]>
Co-authored-by: Szzrain <[email protected]>
Co-authored-by: Szzrain <[email protected]>
Co-authored-by: Xiangze Li <[email protected]>
  • Loading branch information
4 people authored Apr 2, 2024
1 parent 2f9b61c commit 60eef91
Show file tree
Hide file tree
Showing 7 changed files with 427 additions and 62 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ temp/

dist/
go-cqhttp/
lagrange/
frontend/
frontend-overwrite/
backups/
Expand Down
3 changes: 2 additions & 1 deletion api/api_bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func Bind(e *echo.Echo, _myDice *dice.DiceManager) {
e.POST(prefix+"/im_connections/sms_code_get", ImConnectionsSmsCodeGet)
e.POST(prefix+"/im_connections/sms_code_set", ImConnectionsSmsCodeSet)
e.POST(prefix+"/im_connections/gocq_captcha_set", ImConnectionsCaptchaSet)
e.POST(prefix+"/im_connections/add", ImConnectionsAdd)
e.POST(prefix+"/im_connections/add", ImConnectionsAddBuiltinGocq)
e.POST(prefix+"/im_connections/addOnebot11ReverseWs", ImConnectionsAddReverseWs)
e.POST(prefix+"/im_connections/addGocqSeparate", ImConnectionsAddGocqSeparate)
e.POST(prefix+"/im_connections/addDiscord", ImConnectionsAddDiscord)
Expand All @@ -400,6 +400,7 @@ func Bind(e *echo.Echo, _myDice *dice.DiceManager) {
e.POST(prefix+"/im_connections/addOfficialQQ", ImConnectionsAddOfficialQQ)
e.POST(prefix+"/im_connections/addSealChat", ImConnectionsAddSealChat)
e.POST(prefix+"/im_connections/addSatori", ImConnectionsAddSatori)
e.POST(prefix+"/im_connections/addLagrange", ImConnectionsAddBuiltinLagrange)
e.POST(prefix+"/im_connections/del", ImConnectionsDel)
e.POST(prefix+"/im_connections/set_enable", ImConnectionsSetEnable)
e.POST(prefix+"/im_connections/set_data", ImConnectionsSetData)
Expand Down
49 changes: 47 additions & 2 deletions api/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"sort"
"strconv"
"time"

"github.com/labstack/echo/v4"
Expand Down Expand Up @@ -163,7 +164,7 @@ func ImConnectionsDel(c echo.Context) error {
case "QQ":
myDice.ImSession.EndPoints = append(myDice.ImSession.EndPoints[:index], myDice.ImSession.EndPoints[index+1:]...)
if i.ProtocolType == "onebot" {
dice.GoCqhttpServeProcessKill(myDice, i)
dice.BuiltinQQServeProcessKill(myDice, i)
}
return c.JSON(http.StatusOK, i)
case "DISCORD":
Expand Down Expand Up @@ -661,7 +662,7 @@ func ImConnectionsAddSlack(c echo.Context) error {
return c.String(430, "")
}

func ImConnectionsAdd(c echo.Context) error {
func ImConnectionsAddBuiltinGocq(c echo.Context) error {
if !doAuth(c) {
return c.JSON(http.StatusForbidden, nil)
}
Expand Down Expand Up @@ -905,3 +906,47 @@ func ImConnectionsAddSatori(c echo.Context) error {
go dice.ServeQQ(myDice, conn)
return Success(&c, Response{})
}

func ImConnectionsAddBuiltinLagrange(c echo.Context) error {
if !doAuth(c) {
return c.JSON(http.StatusForbidden, nil)
}
if dm.JustForTest {
return c.JSON(http.StatusOK, Response{"testMode": true})
}

v := struct {
Account string `yaml:"account" json:"account"`
Protocol int `yaml:"protocol" json:"protocol"`
}{}
err := c.Bind(&v)
if err == nil {
uid := v.Account
for _, i := range myDice.ImSession.EndPoints {
if i.UserID == dice.FormatDiceIDQQ(uid) {
return c.JSON(CodeAlreadyExists, i)
}
}
conn := dice.NewLagrangeConnectInfoItem(v.Account)
conn.UserID = dice.FormatDiceIDQQ(uid)
conn.Session = myDice.ImSession
pa := conn.Adapter.(*dice.PlatformAdapterGocq)
pa.InPackGoCqhttpProtocol = v.Protocol
pa.Session = myDice.ImSession

myDice.ImSession.EndPoints = append(myDice.ImSession.EndPoints, conn)
myDice.LastUpdatedTime = time.Now().Unix()
uin, err := strconv.ParseInt(v.Account, 10, 64)
if err != nil {
return err
}
dice.LagrangeServe(myDice, conn, dice.GoCqhttpLoginInfo{
Protocol: v.Protocol,
UIN: uin,
IsAsyncRun: true,
})
return c.JSON(http.StatusOK, v)
}

return c.String(430, "")
}
96 changes: 63 additions & 33 deletions dice/platform_adapter_gocq.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ type PlatformAdapterGocq struct {
ConnectURL string `yaml:"connectUrl" json:"connectUrl"` // 连接地址
AccessToken string `yaml:"accessToken" json:"accessToken"` // 访问令牌

UseInPackGoCqhttp bool `yaml:"useInPackGoCqhttp" json:"useInPackGoCqhttp"` // 是否使用内置的gocqhttp
GoCqhttpState int `yaml:"-" json:"loginState"` // 当前状态
CurLoginIndex int `yaml:"-" json:"curLoginIndex"` // 当前登录序号,如果正在进行的登录不是该Index,证明过时
UseInPackGoCqhttp bool `yaml:"useInPackGoCqhttp" json:"useInPackGoCqhttp"` // 是否使用内置的gocqhttp
BuiltinMode string `yaml:"builtinMode" json:"builtinMode"`
GoCqhttpState int `yaml:"-" json:"loginState"` // 当前状态
CurLoginIndex int `yaml:"-" json:"curLoginIndex"` // 当前登录序号,如果正在进行的登录不是该Index,证明过时

GoCqhttpProcess *procs.Process `yaml:"-" json:"-"`
GocqhttpLoginFailedReason string `yaml:"-" json:"curLoginFailedReason"` // 当前登录失败原因
Expand Down Expand Up @@ -355,7 +356,11 @@ func OneBot11CqMessageToArrayMessage(longText string) []interface{} {
}

func (pa *PlatformAdapterGocq) Serve() int {
pa.Implementation = "gocq"
if pa.BuiltinMode == "lagrange" {
pa.Implementation = "lagrange"
} else {
pa.Implementation = "gocq"
}
ep := pa.EndPoint
s := pa.Session
log := s.Parent.Logger
Expand Down Expand Up @@ -388,6 +393,7 @@ func (pa *PlatformAdapterGocq) Serve() int {
// if CheckDialErr(err) != syscall.ECONNREFUSED {
// refused 不算大事
log.Error("onebot v11 connection error: ", err)
log.Info("onebot wss connection addr: ", socket.Url)
// }
pa.InPackGoCqhttpDisconnectedCH <- 2
}
Expand Down Expand Up @@ -1133,24 +1139,40 @@ func (pa *PlatformAdapterGocq) DoRelogin() bool {
if pa.InPackGoCqhttpDisconnectedCH != nil {
pa.InPackGoCqhttpDisconnectedCH <- -1
}
myDice.Logger.Infof("重新启动go-cqhttp进程,对应账号: <%s>(%s)", ep.Nickname, ep.UserID)
pa.CurLoginIndex++
pa.GoCqhttpState = StateCodeInit
go GoCqhttpServeProcessKill(myDice, ep)
time.Sleep(10 * time.Second) // 上面那个清理有概率卡住,具体不懂,改成等5s -> 10s 超过一次重试间隔
GoCqhttpServeRemoveSessionToken(myDice, ep) // 删除session.token
pa.GoCqhttpLastRestrictedTime = 0 // 重置风控时间
myDice.LastUpdatedTime = time.Now().Unix()
myDice.Save(false)
GoCqhttpServe(myDice, ep, GoCqhttpLoginInfo{
Password: pa.InPackGoCqhttpPassword,
Protocol: pa.InPackGoCqhttpProtocol,
AppVersion: pa.InPackGoCqhttpAppVersion,
IsAsyncRun: true,
UseSignServer: pa.UseSignServer,
SignServerConfig: pa.SignServerConfig,
})
return true
if pa.BuiltinMode == "lagrange" {
myDice.Logger.Infof("重新启动 lagrange 进程,对应账号: <%s>(%s)", ep.Nickname, ep.UserID)
pa.CurLoginIndex++
pa.GoCqhttpState = StateCodeInit
go BuiltinQQServeProcessKill(myDice, ep)
time.Sleep(10 * time.Second) // 上面那个清理有概率卡住,具体不懂,改成等5s -> 10s 超过一次重试间隔
LagrangeServeRemoveSession(myDice, ep) // 删除 keystore
pa.GoCqhttpLastRestrictedTime = 0 // 重置风控时间
myDice.LastUpdatedTime = time.Now().Unix()
myDice.Save(false)
GoCqhttpServe(myDice, ep, GoCqhttpLoginInfo{
IsAsyncRun: true,
})
return true
} else {
myDice.Logger.Infof("重新启动go-cqhttp进程,对应账号: <%s>(%s)", ep.Nickname, ep.UserID)
pa.CurLoginIndex++
pa.GoCqhttpState = StateCodeInit
go BuiltinQQServeProcessKill(myDice, ep)
time.Sleep(10 * time.Second) // 上面那个清理有概率卡住,具体不懂,改成等5s -> 10s 超过一次重试间隔
GoCqhttpServeRemoveSessionToken(myDice, ep) // 删除session.token
pa.GoCqhttpLastRestrictedTime = 0 // 重置风控时间
myDice.LastUpdatedTime = time.Now().Unix()
myDice.Save(false)
GoCqhttpServe(myDice, ep, GoCqhttpLoginInfo{
Password: pa.InPackGoCqhttpPassword,
Protocol: pa.InPackGoCqhttpProtocol,
AppVersion: pa.InPackGoCqhttpAppVersion,
IsAsyncRun: true,
UseSignServer: pa.UseSignServer,
SignServerConfig: pa.SignServerConfig,
})
return true
}
}
return false
}
Expand All @@ -1163,16 +1185,24 @@ func (pa *PlatformAdapterGocq) SetEnable(enable bool) {
pa.DiceServing = false

if pa.UseInPackGoCqhttp {
GoCqhttpServeProcessKill(d, c)
time.Sleep(1 * time.Second)
GoCqhttpServe(d, c, GoCqhttpLoginInfo{
Password: pa.InPackGoCqhttpPassword,
Protocol: pa.InPackGoCqhttpProtocol,
AppVersion: pa.InPackGoCqhttpAppVersion,
IsAsyncRun: true,
UseSignServer: pa.UseSignServer,
SignServerConfig: pa.SignServerConfig,
})
if pa.BuiltinMode == "lagrange" {
BuiltinQQServeProcessKill(d, c)
time.Sleep(1 * time.Second)
LagrangeServe(d, c, GoCqhttpLoginInfo{
IsAsyncRun: true,
})
} else {
BuiltinQQServeProcessKill(d, c)
time.Sleep(1 * time.Second)
GoCqhttpServe(d, c, GoCqhttpLoginInfo{
Password: pa.InPackGoCqhttpPassword,
Protocol: pa.InPackGoCqhttpProtocol,
AppVersion: pa.InPackGoCqhttpAppVersion,
IsAsyncRun: true,
UseSignServer: pa.UseSignServer,
SignServerConfig: pa.SignServerConfig,
})
}
go ServeQQ(d, c)
} else {
go ServeQQ(d, c)
Expand All @@ -1181,7 +1211,7 @@ func (pa *PlatformAdapterGocq) SetEnable(enable bool) {
c.Enable = false
pa.DiceServing = false
if pa.UseInPackGoCqhttp {
GoCqhttpServeProcessKill(d, c)
BuiltinQQServeProcessKill(d, c)
}
if pa.IsReverse && pa.reverseApp != nil {
_ = pa.reverseApp.Close()
Expand Down
57 changes: 40 additions & 17 deletions dice/platform_adapter_gocq_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,30 +487,52 @@ func NewGoCqhttpConnectInfoItem(account string) *EndPointInfo {
conn.Adapter = &PlatformAdapterGocq{
EndPoint: conn,
UseInPackGoCqhttp: true,
BuiltinMode: "gocq",
}
return conn
}

func GoCqhttpServeProcessKill(dice *Dice, conn *EndPointInfo) {
defer func() {
func BuiltinQQServeProcessKill(dice *Dice, conn *EndPointInfo) {
go func() {
defer func() {
if r := recover(); r != nil {
dice.Logger.Error("go-cqhttp清理报错: ", r)
// go-cqhttp 进程退出: exit status 1
dice.Logger.Error("内置 QQ 客户端清理报错: ", r)
// go-cqhttp/lagrange 进程退出: exit status 1
}
}()

pa, ok := conn.Adapter.(*PlatformAdapterGocq)
if !ok {
return
}
if pa.UseInPackGoCqhttp {
// 重置状态
conn.State = 0
pa.GoCqhttpState = 0
if !pa.UseInPackGoCqhttp {
return
}

// 重置状态
conn.State = 0
pa.GoCqhttpState = 0
pa.DiceServing = false
pa.GoCqhttpQrcodeData = nil

if pa.BuiltinMode == "lagrange" {
workDir := lagrangeGetWorkDir(dice, conn)
qrcodeFile := filepath.Join(workDir, fmt.Sprintf("qr-%s.png", conn.UserID[3:]))
if _, err := os.Stat(qrcodeFile); err == nil {
// 如果已经存在二维码文件,将其删除
_ = os.Remove(qrcodeFile)
dice.Logger.Info("onebot: 删除已存在的二维码文件")
}

pa.DiceServing = false
pa.GoCqhttpQrcodeData = nil
// 注意这个会panic,因此recover捕获了
if pa.GoCqhttpProcess != nil {
p := pa.GoCqhttpProcess
pa.GoCqhttpProcess = nil
// sigintwindows.SendCtrlBreak(p.Cmds[0].Process.Pid)
_ = p.Stop()
_ = p.Wait() // 等待进程退出,因为Stop内部是Kill,这是不等待的
}
} else {
pa.GoCqhttpLoginDeviceLockURL = ""

workDir := gocqGetWorkDir(dice, conn)
Expand Down Expand Up @@ -546,6 +568,7 @@ func GoCqhttpServeRemoveSessionToken(dice *Dice, conn *EndPointInfo) {
}

type GoCqhttpLoginInfo struct {
UIN int64
Password string
Protocol int
AppVersion string
Expand Down Expand Up @@ -580,7 +603,7 @@ func GoCqhttpServe(dice *Dice, conn *EndPointInfo, loginInfo GoCqhttpLoginInfo)
loginIndex := pa.CurLoginIndex
pa.GoCqhttpState = StateCodeInLogin

if pa.UseInPackGoCqhttp { //nolint:nestif
if pa.UseInPackGoCqhttp && pa.BuiltinMode == "gocq" { //nolint:nestif
workDir := gocqGetWorkDir(dice, conn)
_ = os.MkdirAll(workDir, 0o755)

Expand Down Expand Up @@ -636,7 +659,7 @@ func GoCqhttpServe(dice *Dice, conn *EndPointInfo, loginInfo GoCqhttpLoginInfo)
// 启动客户端
wd, _ := os.Getwd()
gocqhttpExePath, _ := filepath.Abs(filepath.Join(wd, "go-cqhttp/go-cqhttp"))
gocqhttpExePath = strings.ReplaceAll(gocqhttpExePath, "\\", "/") // windows平台需要这个替换
gocqhttpExePath = filepath.ToSlash(gocqhttpExePath) // windows平台需要这个替换

// 随手执行一下
_ = os.Chmod(gocqhttpExePath, 0o755)
Expand All @@ -650,7 +673,7 @@ func GoCqhttpServe(dice *Dice, conn *EndPointInfo, loginInfo GoCqhttpLoginInfo)
}
chQrCode := make(chan int, 1)
riskCount := 0
isSeldKilling := false
isSelfKilling := false

slideMode := 0
chSMS := make(chan string, 1)
Expand All @@ -659,10 +682,10 @@ func GoCqhttpServe(dice *Dice, conn *EndPointInfo, loginInfo GoCqhttpLoginInfo)
p.OutputHandler = func(line string) string {
if loginIndex != pa.CurLoginIndex {
// 当前连接已经无用,进程自杀
if !isSeldKilling {
if !isSelfKilling {
dice.Logger.Infof("检测到新的连接序号 %d,当前连接 %d 将自动退出", pa.CurLoginIndex, loginIndex)
// 注: 这里不要调用kill
isSeldKilling = true
isSelfKilling = true
_ = p.Stop()
}
return ""
Expand Down Expand Up @@ -948,7 +971,7 @@ func GoCqhttpServe(dice *Dice, conn *EndPointInfo, loginInfo GoCqhttpLoginInfo)
isDeviceLockLogin := pa.GoCqhttpState == StateCodeInLoginDeviceLock
if !isDeviceLockLogin {
// 如果在设备锁流程中,不清空数据
GoCqhttpServeProcessKill(dice, conn)
BuiltinQQServeProcessKill(dice, conn)

if isInLogin {
conn.State = 3
Expand All @@ -971,7 +994,7 @@ func GoCqhttpServe(dice *Dice, conn *EndPointInfo, loginInfo GoCqhttpLoginInfo)
} else {
run()
}
} else {
} else if !pa.UseInPackGoCqhttp {
pa.GoCqhttpState = StateCodeLoginSuccessed
pa.GoCqhttpLoginSucceeded = true
dice.Save(false)
Expand Down
Loading

0 comments on commit 60eef91

Please sign in to comment.