Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 公骰列表后端基础功能 #1145

Merged
merged 5 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 101 additions & 2 deletions dice/dice.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"sealdice-core/dice/logger"
"sealdice-core/dice/model"
log "sealdice-core/utils/kratos"
"sealdice-core/utils/public_dice"
)

type CmdExecuteResult struct {
Expand Down Expand Up @@ -183,12 +184,15 @@ type Dice struct {

CensorManager *CensorManager `json:"-" yaml:"-"`

Config Config `json:"-" yaml:"-"`

AttrsManager *AttrsManager `json:"-" yaml:"-"`

Config Config `json:"-" yaml:"-"`

AdvancedConfig AdvancedConfig `json:"-" yaml:"-"`

PublicDice *public_dice.PublicDiceClient `json:"-" yaml:"-"`
PublicDiceTimerId cron.EntryID `json:"-" yaml:"-"`

ContainerMode bool `yaml:"-" json:"-"` // 容器模式:禁用内置适配器,不允许使用内置Lagrange和旧的内置Gocq

IsAlreadyLoadConfig bool `yaml:"-"` // 如果在loads前崩溃,那么不写入配置,防止覆盖为空的
Expand Down Expand Up @@ -263,6 +267,8 @@ func (d *Dice) Init() {
d.NewCensorManager()
}

go d.PublicDiceSetup()

// 创建js运行时
if d.Config.JsEnable {
d.Logger.Info("js扩展支持:开启")
Expand Down Expand Up @@ -719,3 +725,96 @@ func (d *Dice) ResetQuitInactiveCron() {
d.Logger.Infof("退群功能已启动,每 %s 执行一次退群判定", duration.String())
}
}

func (d *Dice) PublicDiceEndpointRefresh() {
cfg := &d.Config.PublicDiceConfig

var endpointItems []*public_dice.Endpoint
for _, i := range d.ImSession.EndPoints {
fy0 marked this conversation as resolved.
Show resolved Hide resolved
if !i.IsPublic {
continue
}
endpointItems = append(endpointItems, &public_dice.Endpoint{
Platform: i.Platform,
UID: i.UserID,
IsOnline: i.State == 1,
})
}

_, code := d.PublicDice.EndpointUpdate(&public_dice.EndpointUpdateRequest{
DiceID: cfg.ID,
Endpoints: endpointItems,
}, GenerateVerificationKeyForPublicDice)
if code != 200 {
log.Warn("[公骰]无法通过服务器校验,不再进行更新")
return
}
}

func (d *Dice) PublicDiceInfoRegister() {
cfg := &d.Config.PublicDiceConfig

pd, code := d.PublicDice.Register(&public_dice.RegisterRequest{
ID: cfg.ID,
Name: cfg.Name,
Brief: cfg.Brief,
Note: cfg.Note,
}, GenerateVerificationKeyForPublicDice)
if code != 200 {
log.Warn("[公骰]无法通过服务器校验,不再进行骰号注册")
return
}
// 两种可能: 1. 原本ID为空 2. ID 无效,这里会自动变成新的
if pd.Item.ID != "" && cfg.ID != pd.Item.ID {
cfg.ID = pd.Item.ID
}
}

func (d *Dice) PublicDiceSetupTick() {
cfg := &d.Config.PublicDiceConfig

doTickUpdate := func() {
if !cfg.Enable {
d.Cron.Remove(d.PublicDiceTimerId)
return
}
var tickEndpointItems []*public_dice.TickEndpoint
for _, i := range d.ImSession.EndPoints {
if !i.IsPublic {
continue
}
tickEndpointItems = append(tickEndpointItems, &public_dice.TickEndpoint{
UID: i.UserID,
IsOnline: i.State == 1,
})
}
d.PublicDice.TickUpdate(&public_dice.TickUpdateRequest{
ID: cfg.ID,
Endpoints: tickEndpointItems,
}, GenerateVerificationKeyForPublicDice)
}

if d.PublicDiceTimerId != 0 {
d.Cron.Remove(d.PublicDiceTimerId)
}

go func() {
// 20s后进行第一次调用,此后3min进行一次更新
time.Sleep(20 * time.Second)
doTickUpdate()
}()

d.PublicDiceTimerId, _ = d.Cron.AddFunc("@every 3m", doTickUpdate)
}

func (d *Dice) PublicDiceSetup() {
d.PublicDice = public_dice.NewClient("https://dice.weizaima.com", "")

cfg := &d.Config.PublicDiceConfig
if !cfg.Enable {
return
}
d.PublicDiceInfoRegister()
d.PublicDiceEndpointRefresh()
d.PublicDiceSetupTick()
}
11 changes: 11 additions & 0 deletions dice/dice_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ type Config struct {
NewsConfig `yaml:",inline"`
// 敏感词设置
CensorConfig `yaml:",inline"`
// 公骰设置
PublicDiceConfig `yaml:",inline"`

// 其它设置,包含由于被导出无法从 Dice 上迁移过来的配置项,为了在 DefaultConfig 上统一设置默认值增加此结构
DirtyConfig `yaml:",inline"`
Expand Down Expand Up @@ -223,6 +225,15 @@ type NewsConfig struct {
NewsMark string `json:"newsMark" yaml:"newsMark"` // 已读新闻的md5
}

type PublicDiceConfig struct {
Enable bool `json:"publicDiceEnable" yaml:"publicDiceEnable"`
ID string `json:"publicDiceId" yaml:"publicDiceId"`
Name string `json:"publicDiceName" yaml:"publicDiceName"`
Brief string `json:"publicDiceBrief" yaml:"publicDiceBrief"`
Note string `json:"publicDiceNote" yaml:"publicDiceNote"`
Avatar string `json:"publicDiceAvatar" yaml:"publicDiceAvatar"`
}

type CensorConfig struct {
EnableCensor bool `json:"enableCensor" yaml:"enableCensor"` // 启用敏感词审查
CensorMode CensorMode `json:"censorMode" yaml:"censorMode"`
Expand Down
3 changes: 3 additions & 0 deletions dice/dice_config_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ var DefaultConfig = Config{
CensorMatchPinyin: false,
CensorFilterRegexStr: "",
},
PublicDiceConfig{
Enable: false,
},
DirtyConfig{
DeckList: nil,
CommandPrefix: []string{
Expand Down
35 changes: 35 additions & 0 deletions dice/verify.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dice

import (
"crypto/sha256"
"encoding/base64"
"fmt"
"os"
Expand Down Expand Up @@ -71,3 +72,37 @@ func GenerateVerificationCode(platform string, userID string, username string, u
return fmt.Sprintf("SEAL%%%s", base2048.DefaultEncoding.EncodeToString(dp))
}
}

type payloadPublicDice struct {
Version string `msgpack:"version,omitempty"`
Sign []byte `msgpack:"sign,omitempty"`
}

func GenerateVerificationKeyForPublicDice(data any) string {
doEcdsaSign := len(SealTrustedClientPrivateKey) > 0
pp, _ := msgpack.Marshal(data)

var sign []byte
if doEcdsaSign {
var err error
sign, err = crypto.EcdsaSignRow(pp, SealTrustedClientPrivateKey)
if err != nil {
return ""
}
} else {
h := sha256.New()
h.Write(pp)
sign = h.Sum(nil)
}
fy0 marked this conversation as resolved.
Show resolved Hide resolved

d := payloadPublicDice{
Version: VERSION.String(),
Sign: sign,
}

dp, _ := msgpack.Marshal(d)
if doEcdsaSign {
return fmt.Sprintf("SEAL#%s", base64.StdEncoding.EncodeToString(dp))
}
return fmt.Sprintf("SEAL~%s", base64.StdEncoding.EncodeToString(dp))
}
165 changes: 165 additions & 0 deletions utils/public_dice/sdk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package public_dice

import (
"github.com/monaco-io/request"
)

// PublicDiceClient SDK客户端
type PublicDiceClient struct {
baseURL string
token string
}

// NewClient 创建新的SDK客户端
func NewClient(baseURL string, token string) *PublicDiceClient {
return &PublicDiceClient{
baseURL: baseURL,
token: token,
}
}

// doReq 发送HTTP请求
func doReq[T any](c *PublicDiceClient, method string, path string, data any, params map[string]string) (*T, int) {
req := request.Client{
URL: c.baseURL + path,
Method: method,
JSON: data,
Query: params,
}

var result T
resp := req.Send()
resp.Scan(&result)

return &result, resp.Code()
}

// Endpoint 公骰终端信息
type Endpoint struct {
Platform string `json:"platform" msgpack:",omitempty"`
UID string `json:"uid" msgpack:",omitempty"`
InviteURL string `json:"inviteUrl" msgpack:",omitempty"`
IsOnline bool `json:"isOnline" msgpack:",omitempty"`

ID string `json:"id" msgpack:",omitempty"`
CreatedAt string `json:"createdAt" msgpack:",omitempty"`
UpdatedAt string `json:"updatedAt" msgpack:",omitempty"`
LastTickTime int64 `json:"lastTickTime" msgpack:",omitempty"`
}

// DiceInfo 公骰信息
type DiceInfo struct {
ID string `json:"id" msgpack:",omitempty"`
CreatedAt string `json:"createdAt" msgpack:",omitempty"`
UpdatedAt string `json:"updatedAt" msgpack:",omitempty"`
Name string `json:"name" msgpack:",omitempty"`
Brief string `json:"brief" msgpack:",omitempty"`
Note string `json:"note" msgpack:",omitempty"`
Avatar string `json:"avatar" msgpack:",omitempty"`
Version string `json:"version" msgpack:",omitempty"`
IsOfficialVersion bool `json:"isOfficialVersion" msgpack:",omitempty"`
UpdateTickCount int `json:"updateTickCount" msgpack:",omitempty"`
LastTickTime int64 `json:"lastTickTime" msgpack:",omitempty"`
Endpoints []*Endpoint `json:"endpoints" msgpack:",omitempty"`
}

// ListResponse 公骰列表响应
type ListResponse struct {
Items []*DiceInfo `json:"items"`
}

// ListGet 获取公骰列表
func (c *PublicDiceClient) ListGet(keyFunc func(data any) string) (*ListResponse, int) {
if keyFunc != nil {
data := keyFunc(nil)
return doReq[ListResponse](c, "GET", "/dice/api/public-dice/list", data, nil)
}
return doReq[ListResponse](c, "GET", "/dice/api/public-dice/list", nil, nil)
}

// RegisterRequest 注册公骰请求
type RegisterRequest struct {
ID string `json:"ID,omitempty" msgpack:",omitempty"`
Name string `json:"name,omitempty" msgpack:",omitempty"` // 15字
Brief string `json:"brief,omitempty" msgpack:",omitempty"`
Note string `json:"note,omitempty" msgpack:",omitempty"`
Avatar string `json:"avatar,omitempty" msgpack:",omitempty"` // 头像?还是用另一个api进行注册比较好?可以省略
Key string `json:"key,omitempty" msgpack:",omitempty"`
}

// RegisterResponse 注册公骰响应
type RegisterResponse struct {
Item DiceInfo `json:"item"`
}

// Register 注册公骰
func (c *PublicDiceClient) Register(req *RegisterRequest, keyFunc func(data any) string) (*RegisterResponse, int) {
if keyFunc != nil {
req.Key = keyFunc(req)
}
return doReq[RegisterResponse](c, "POST", "/dice/api/public-dice/register", req, nil)
}

// DiceUpdateRequest 更新公骰请求
type DiceUpdateRequest struct {
ID string `json:"id" msgpack:",omitempty"`
Name string `json:"name" msgpack:",omitempty"`
Brief string `json:"brief" msgpack:",omitempty"`
Note string `json:"note" msgpack:",omitempty"`
Key string `json:"key" msgpack:",omitempty"`
}

// DiceUpdateResponse 更新公骰响应
type DiceUpdateResponse struct {
Updated int `json:"updated"`
}

// DiceUpdate 更新公骰
func (c *PublicDiceClient) DiceUpdate(req *DiceUpdateRequest, keyFunc func(data any) string) (*DiceUpdateResponse, int) {
if keyFunc != nil {
req.Key = keyFunc(req)
}
return doReq[DiceUpdateResponse](c, "POST", "/dice/api/public-dice/register?update=1", req, nil)
}

// EndpointUpdateRequest 更新公骰SNS账号信息请求
type EndpointUpdateRequest struct {
DiceID string `json:"diceId" msgpack:",omitempty"`
Key string `json:"key" msgpack:",omitempty"`
Endpoints []*Endpoint `json:"endpoints" msgpack:",omitempty"`
}

// EndpointUpdateResponse 更新公骰SNS账号信息响应
type EndpointUpdateResponse struct{}

// EndpointUpdate 更新公骰SNS账号信息
func (c *PublicDiceClient) EndpointUpdate(req *EndpointUpdateRequest, keyFunc func(data any) string) (*EndpointUpdateResponse, int) {
if keyFunc != nil {
req.Key = keyFunc(req)
}
return doReq[EndpointUpdateResponse](c, "POST", "/dice/api/public-dice/endpoint-update", req, nil)
}

// TickUpdateRequest 更新公骰心跳请求
type TickUpdateRequest struct {
ID string `json:"ID" msgpack:",omitempty"`
Key string `json:"key" msgpack:",omitempty"`
Endpoints []*TickEndpoint `json:"Endpoints" msgpack:",omitempty"`
}

// TickEndpoint 公骰心跳端点信息
type TickEndpoint struct {
UID string `json:"uid" msgpack:",omitempty"`
IsOnline bool `json:"isOnline" msgpack:",omitempty"`
}

// TickUpdateResponse 更新公骰心跳响应
type TickUpdateResponse struct{}

// TickUpdate 更新公骰心跳
func (c *PublicDiceClient) TickUpdate(req *TickUpdateRequest, keyFunc func(data any) string) (*TickUpdateResponse, int) {
if keyFunc != nil {
req.Key = keyFunc(req)
}
return doReq[TickUpdateResponse](c, "POST", "/dice/api/public-dice/tick-update", req, nil)
}