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(Store): 扩展商店对应的 api 支持 #823

Closed
wants to merge 7 commits into from
Closed
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ _help_cache

.vscode/
!.vscode/settings.json

/signature/**/*
5 changes: 5 additions & 0 deletions api/api_bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,4 +697,9 @@ func Bind(e *echo.Echo, _myDice *dice.DiceManager) {
e.GET(prefix+"/resource/data", resourceGetData)

e.GET(prefix+"/verify/generate_code", verifyGenerateCode)

e.GET(prefix+"/store/recommend", storeRecommend)
e.GET(prefix+"/store/page", storeGetPage)
e.POST(prefix+"/store/download", storeDownload)
e.POST(prefix+"/store/rating", storeRating)
}
100 changes: 100 additions & 0 deletions api/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package api

import (
"github.com/labstack/echo/v4"

"sealdice-core/dice"
)

var storeCache = make(map[string]*dice.StoreExt)

func checkInstalled(exts []*dice.StoreExt) {
for _, ext := range exts {
switch ext.Type {
case dice.StoreExtTypeDeck:
ext.Installed = myDice.InstalledDecks[ext.ID]
case dice.StoreExtTypePlugin:
ext.Installed = myDice.InstalledPlugins[ext.ID]
default:
// pass
}
if len(ext.ID) > 0 {
storeCache[ext.ID] = ext
}
}
}

func storeRecommend(c echo.Context) error {
data, err := myDice.StoreQueryRecommend()
if err != nil {
return Error(&c, err.Error(), Response{})
}
checkInstalled(data)
for _, elem := range data {
storeCache[elem.ID] = elem
}
return Success(&c, Response{
"data": data,
})
}

func storeGetPage(c echo.Context) error {
params := dice.StoreQueryPageParams{}
err := c.Bind(&params)
if err != nil {
return Error(&c, err.Error(), Response{})
}

page, err := myDice.StoreQueryPage(params)
if err != nil {
return Error(&c, err.Error(), Response{})
}
data := page.Data
checkInstalled(data)
for _, elem := range data {
storeCache[elem.ID] = elem
}
return Success(&c, Response{
"data": data,
"pageNum": page.PageNum,
"pageSize": page.PageSize,
"next": page.Next,
})
}

func storeDownload(c echo.Context) error {
var params struct {
ID string `json:"id"`
}
err := c.Bind(&params)
if err != nil {
return Error(&c, err.Error(), Response{})
}

target := storeCache[params.ID]
if target.Installed {
return Error(&c, "请勿重复安装", Response{})
}
switch target.Type {
case dice.StoreExtTypeDeck:
err = myDice.DeckDownload(target.Name, target.Ext, target.DownloadUrl, target.Hash)
if err != nil {
return Error(&c, err.Error(), Response{})
}
target.Installed = true
return Success(&c, Response{})
case dice.StoreExtTypePlugin:
err = myDice.JsDownload(target.Name, target.DownloadUrl, target.Hash)
if err != nil {
return Error(&c, err.Error(), Response{})
}
target.Installed = true
return Success(&c, Response{})
default:
return Error(&c, "该类型的扩展目前不支持下载", Response{})
}
}

func storeRating(c echo.Context) error {
return Error(&c, "not implemented", Response{})
}
8 changes: 8 additions & 0 deletions dice/dice.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ type Dice struct {
AdvancedConfig AdvancedConfig `json:"-" yaml:"-"`

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

/* 已安装的商店扩展。记录各扩展读取时识别到的商店ID,用于扩展商店判断是否已安装对应扩展(用 map 代替 set) */
InstalledPlugins map[string]bool `yaml:"-" json:"-"`
InstalledDecks map[string]bool `yaml:"-" json:"-"`
InstalledReplies map[string]bool `yaml:"-" json:"-"`
}

type CensorMode int
Expand Down Expand Up @@ -336,6 +341,9 @@ func (d *Dice) Init() {
d.Cron.Start()

d.CocExtraRules = map[int]*CocRuleInfo{}
d.InstalledPlugins = map[string]bool{}
d.InstalledDecks = map[string]bool{}
d.InstalledReplies = map[string]bool{}

var err error
d.DBData, d.DBLogs, err = model.SQLiteDBInit(d.BaseConfig.DataDir)
Expand Down
2 changes: 2 additions & 0 deletions dice/dice_advanced_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ type AdvancedConfig struct {
StoryLogBackendUrl string `json:"storyLogBackendUrl" yaml:"storyLogBackendUrl"` // 自定义后端地址
StoryLogApiVersion string `json:"storyLogApiVersion" yaml:"storyLogApiVersion"` // 后端 api 版本
StoryLogBackendToken string `json:"storyLogBackendToken" yaml:"storyLogBackendToken"` // 自定义后端 token

StoreBackendUrl string `json:"storeBackendUrl" yaml:"storeBackendUrl"` // 自定义商店后端地址
}
53 changes: 52 additions & 1 deletion dice/dice_jsvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,9 @@ func (d *Dice) jsClear() {
// 清理规则模板
d.GameSystemMap = &SyncMap[string, *GameSystemTemplate]{}
d.RegisterBuiltinSystemTemplate()
// 关闭js vm
// 清理已安装列表
d.InstalledPlugins = map[string]bool{}
// 关闭js
if d.JsLoop != nil {
d.JsLoop.Stop()
d.JsLoop = nil
Expand Down Expand Up @@ -573,6 +575,9 @@ func (d *Dice) JsLoadScripts() {
return nil
}
jsInfos = append(jsInfos, jsInfo)
if len(jsInfo.StoreID) > 0 {
d.InstalledPlugins[jsInfo.StoreID] = true
}
} else {
d.Logger.Warnf("内置脚本「%s」校验未通过,拒绝加载", path)
}
Expand All @@ -598,6 +603,9 @@ func (d *Dice) JsLoadScripts() {
return nil
}
jsInfos = append(jsInfos, jsInfo)
if len(jsInfo.StoreID) > 0 {
d.InstalledPlugins[jsInfo.StoreID] = true
}
}
return nil
})
Expand Down Expand Up @@ -748,6 +756,8 @@ type JsScriptInfo struct {
Digest string `json:"-"`
/** 依赖项 */
Depends []JsScriptDepends `json:"depends"`
/** 扩展商店唯一 ID */
StoreID string `json:"storeID"`
}

type JsScriptDepends struct {
Expand Down Expand Up @@ -866,6 +876,8 @@ func (d *Dice) JsParseMeta(s string, installTime time.Time, rawData []byte, buil
if !verOK {
errMsg = append(errMsg, fmt.Sprintf("插件「%s」依赖的海豹版本限制在 %s,与海豹版本(%s)的JSAPI不兼容", jsInfo.Name, v, VERSION.String()))
}
case "storeID":
jsInfo.StoreID = v
}
}
jsInfo.UpdateUrls = updateUrls
Expand Down Expand Up @@ -1154,3 +1166,42 @@ func sortJsScripts(jsScripts []*JsScriptInfo) ([]*JsScriptInfo, map[string][]str
}
return result, infos
}

func (d *Dice) JsDownload(name string, url string, hash map[string]string) error {
if len(url) == 0 {
return fmt.Errorf("未提供下载链接")
}
statusCode, data, err := GetCloudContent([]string{url}, "")
if err != nil {
return err
}
if statusCode != http.StatusOK {
return fmt.Errorf("无法获取插件内容")
}

// TODO 检查 hash

// 内容预处理
if isPrefixWithUtf8Bom(data) {
data = data[3:]
}
deck := bytes.ReplaceAll(data, []byte("\r\n"), []byte("\n"))

// TODO 检查签名

target := filepath.Join(d.BaseConfig.DataDir, "scripts", name+".js")
_, err = os.Stat(target)
if !errors.Is(err, os.ErrNotExist) {
d.Logger.Errorf("JS 插件“%s”下载时检查到同名文件", name)
return fmt.Errorf("存在文件名相同的 JS 插件")
}
err = os.WriteFile(target, deck, 0755)
if err != nil {
d.Logger.Errorf("JS 插件“%s”下载时保存文件出错,%s", name, err.Error())
return err
}
d.Logger.Infof("JS 插件“%s”下载成功", name)
d.JsReload()
d.MarkModified()
return nil
}
57 changes: 55 additions & 2 deletions dice/ext_deck.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package dice

import (
"bytes"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -39,6 +40,7 @@ type DeckDiceEFormat struct {
// 一组牌 []string `json:"一组牌"`

// 更新支持字段
StoreID []string `json:"_storeID"`
UpdateUrls []string `json:"_updateUrls"`
Etag []string `json:"_etag"`
}
Expand All @@ -55,8 +57,9 @@ type DeckSinaNyaFormat struct {
// 一组牌 []string `json:"一组牌"`

// 更新支持字段
UpdateUrls []string `json:"update_urls"`
Etag string `json:"etag"`
StoreID string `json:"store_id" yaml:"store_id"`
UpdateUrls []string `json:"update_urls" yaml:"update_urls"`
Etag string `json:"etag" yaml:"etag"`
}

type SealMeta struct {
Expand All @@ -70,6 +73,7 @@ type SealMeta struct {
Desc string `toml:"desc"`
FormatVersion int64 `toml:"format_version"`

StoreID string `toml:"store_id"`
UpdateUrls []string `toml:"update_urls"`
Etag string `toml:"etag"`
}
Expand Down Expand Up @@ -121,6 +125,7 @@ type DeckInfo struct {
Etag string `yaml:"etag" json:"etag"`
Cloud bool `yaml:"cloud" json:"cloud"` // 含有云端内容
CloudDeckItemInfos map[string]*CloudDeckItemInfo `yaml:"-" json:"-"`
StoreID string `yaml:"storeID" json:"storeID"`
}

func tryParseDiceE(content []byte, deckInfo *DeckInfo, jsoncDirectly bool) error {
Expand Down Expand Up @@ -221,6 +226,9 @@ func tryParseDiceE(content []byte, deckInfo *DeckInfo, jsoncDirectly bool) error
deckInfo.FileFormat = "jsonc"
}
deckInfo.Enable = true
if len(jsonData2.StoreID) > 0 {
deckInfo.StoreID = jsonData2.StoreID[0]
}
deckInfo.UpdateUrls = jsonData2.UpdateUrls
if len(jsonData2.Etag) > 0 {
deckInfo.Etag = jsonData2.Etag[0]
Expand Down Expand Up @@ -290,6 +298,7 @@ func tryParseSinaNya(content []byte, deckInfo *DeckInfo) error {
deckInfo.FormatVersion = 1
deckInfo.FileFormat = "yaml"
deckInfo.Enable = true
deckInfo.StoreID = yamlData2.StoreID
deckInfo.UpdateUrls = yamlData2.UpdateUrls
deckInfo.Etag = yamlData2.Etag
return nil
Expand Down Expand Up @@ -393,6 +402,7 @@ func tryParseSeal(content []byte, deckInfo *DeckInfo) error {
deckInfo.FormatVersion = meta.FormatVersion
deckInfo.FileFormat = "toml"
deckInfo.Enable = true
deckInfo.StoreID = meta.StoreID
deckInfo.UpdateUrls = meta.UpdateUrls
deckInfo.Etag = meta.Etag
deckInfo.RawData = &tomlDataFix
Expand Down Expand Up @@ -427,6 +437,9 @@ func DeckTryParse(d *Dice, fn string) {
}

d.DeckList = append(d.DeckList, deckInfo)
if len(deckInfo.StoreID) > 0 {
d.InstalledDecks[deckInfo.StoreID] = true
}
d.MarkModified()
}

Expand Down Expand Up @@ -565,6 +578,7 @@ func DeckReload(d *Dice) {
}
d.IsDeckLoading = true
d.DeckList = d.DeckList[:0]
d.InstalledDecks = map[string]bool{}
d.Logger.Infof("从此目录加载牌堆: %s", "data/decks")
DecksDetect(d)
d.Logger.Infof("加载完成,现有牌堆 %d 个", len(d.DeckList))
Expand Down Expand Up @@ -1223,3 +1237,42 @@ func (d *Dice) DeckUpdate(deckInfo *DeckInfo, tempFileName string) error {
d.Logger.Infof("牌堆“%s”更新成功", deckInfo.Name)
return nil
}

func (d *Dice) DeckDownload(name string, ext string, url string, hash map[string]string) error {
if len(url) == 0 {
return fmt.Errorf("未提供下载链接")
}
statusCode, data, err := GetCloudContent([]string{url}, "")
if err != nil {
return err
}
if statusCode != http.StatusOK {
return fmt.Errorf("无法获取牌堆内容")
}

// TODO 检查 hash

// 内容预处理
if isPrefixWithUtf8Bom(data) {
data = data[3:]
}
deck := bytes.ReplaceAll(data, []byte("\r\n"), []byte("\n"))

// TODO 检查签名

target := filepath.Join("data/decks", name+ext)
_, err = os.Stat(target)
if !errors.Is(err, os.ErrNotExist) {
d.Logger.Errorf("牌堆“%s”下载时检查到同名文件", name)
return fmt.Errorf("存在文件名相同的牌堆")
}
err = os.WriteFile(target, deck, 0755)
if err != nil {
d.Logger.Errorf("牌堆“%s”下载时保存文件出错,%s", name, err.Error())
return err
}
d.Logger.Infof("牌堆“%s”下载成功", name)
DeckReload(d)
d.MarkModified()
return nil
}
4 changes: 4 additions & 0 deletions dice/ext_reply.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ func CustomReplyConfigRead(dice *Dice, filename string) (*ReplyConfig, error) {
if rc.Conditions == nil {
rc.Conditions = []ReplyConditionBase{}
}
if len(rc.StoreID) > 0 {
dice.InstalledReplies[rc.StoreID] = true
}

return rc, nil
}
Expand Down Expand Up @@ -115,6 +118,7 @@ func ReplyReload(dice *Dice) {
return nil
})

dice.InstalledReplies = map[string]bool{}
for _, i := range filenames {
rc, err := CustomReplyConfigRead(dice, i)
if err == nil {
Expand Down
3 changes: 3 additions & 0 deletions dice/ext_reply_logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ type ReplyConfig struct {
UpdateTimestamp int64 `yaml:"updateTimestamp" json:"updateTimestamp"`
Desc string `yaml:"desc" json:"desc"`

// 扩展商店标识
StoreID string `yaml:"storeID" json:"storeID"`

// 文件级别执行条件
Conditions ReplyConditions `yaml:"conditions" json:"conditions"`

Expand Down
Loading
Loading