diff --git a/README.md b/README.md index b283fd73..fb4082bb 100644 --- a/README.md +++ b/README.md @@ -1291,7 +1291,7 @@ print("run[CQ:image,file="+j["img"]+"]")
- WarframeAPI + 星际战甲 `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/warframeapi"` diff --git a/plugin/warframeapi/api.go b/plugin/warframeapi/api.go new file mode 100644 index 00000000..f4cfd608 --- /dev/null +++ b/plugin/warframeapi/api.go @@ -0,0 +1,94 @@ +package warframeapi + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "sort" + + "github.com/FloatTech/floatbox/web" +) + +const wfapiurl = "https://api.warframestat.us/pc" // 星际战甲API +const wfitemurl = "https://api.warframe.market/v1/items" // 星际战甲游戏品信息列表URL + +// 从WFapi获取数据 +func newwfapi() (w wfapi, err error) { + var data []byte + data, err = web.GetData(wfapiurl) + if err != nil { + return + } + err = json.Unmarshal(data, &w) + return +} + +// 获取Warframe市场的售价表,并进行排序,cn_name为物品中文名称,onlyMaxRank表示只取最高等级的物品,返回物品售价表,物品信息,物品英文 +func getitemsorder(cnName string, onlyMaxRank bool) (od orders, it *itemsInSet, n string, err error) { + var wfapiio wfAPIItemsOrders + data, err := web.RequestDataWithHeaders(&http.Client{}, fmt.Sprintf("https://api.warframe.market/v1/items/%s/orders?include=item", cnName), "GET", func(request *http.Request) error { + request.Header.Add("Accept", "application/json") + request.Header.Add("Platform", "pc") + return nil + }, nil) + if err != nil { + return + } + err = json.Unmarshal(data, &wfapiio) + if len(wfapiio.Payload.Orders) <= 0 { + err = errors.New("no such name") + } + od = make(orders, 0, len(wfapiio.Payload.Orders)) + // 遍历市场物品列表 + for _, v := range wfapiio.Payload.Orders { + // 取其中类型为售卖,且去掉不在线的玩家 + if v.OrderType == "sell" && v.User.Status != "offline" { + if !onlyMaxRank { + od = append(od, v) + continue + } + if v.ModRank == wfapiio.Include.Item.ItemsInSet[0].ModMaxRank { + od = append(od, v) + } + } + } + // 对报价表进行排序,由低到高 + sort.Sort(od) + // 获取物品信息 + for i, v := range wfapiio.Include.Item.ItemsInSet { + if v.URLName == cnName { + it = &wfapiio.Include.Item.ItemsInSet[i] + n = v.En.ItemName + return + } + } + it = &wfapiio.Include.Item.ItemsInSet[0] + n = wfapiio.Include.Item.ItemsInSet[0].En.ItemName + return +} + +func newwm() (wmitems map[string]items, itemNames []string) { + var itemapi wfAPIItem // WarFrame市场的数据实例 + + data, err := web.RequestDataWithHeaders(&http.Client{}, wfitemurl, "GET", func(request *http.Request) error { + request.Header.Add("Accept", "application/json") + request.Header.Add("Language", "zh-hans") + return nil + }, nil) + if err != nil { + panic(err) + } + err = json.Unmarshal(data, &itemapi) + if err != nil { + panic(err) + } + + wmitems = make(map[string]items, len(itemapi.Payload.Items)*4) + itemNames = make([]string, len(itemapi.Payload.Items)) + for i, v := range itemapi.Payload.Items { + wmitems[v.ItemName] = v + itemNames[i] = v.ItemName + } + return +} diff --git a/plugin/warframeapi/gametime.go b/plugin/warframeapi/gametime.go deleted file mode 100644 index 3e213ce8..00000000 --- a/plugin/warframeapi/gametime.go +++ /dev/null @@ -1,79 +0,0 @@ -package warframeapi - -import ( - "sync" - "time" - - "github.com/davidscholberg/go-durationfmt" -) - -// 游戏时间模拟 -type gameTime struct { - rwm sync.RWMutex - Name string `json:"name"` // 时间名称 - NextTime time.Time `json:"time"` // 下次更新时间 - Status bool `json:"status"` // 状态 - StatusTrueDes string `json:"true_des"` // 状态说明 - StatusFalseDes string `json:"false_des"` // 状态说明 - DayTime int `json:"day"` // 白天时长 - NightTime int `json:"night"` // 夜间时长 -} - -var ( - gameTimes [3]*gameTime -) - -// TimeString 根据传入的世界编号,获取对应的游戏时间文本 -func (t *gameTime) String() string { - return "平原时间:" + t.daynight() + "\n" + - "下次更新:" + t.remaintime() -} - -// 获取当前游戏时间状态(白天/夜晚) -func (t *gameTime) daynight() string { - t.rwm.RLock() - defer t.rwm.RUnlock() - if t.Status { - return t.StatusTrueDes - } - return t.StatusFalseDes -} - -// 获取下一次时间状态更新的剩余游戏时间(x分x秒) -func (t *gameTime) remaintime() string { - t.rwm.RLock() - d := time.Until(t.NextTime) - t.rwm.RUnlock() - durStr, _ := durationfmt.Format(d, "%m分%s秒后") - return durStr -} - -// 根据API返回内容修正游戏时间 -func loadTime(api wfAPI) { - gameTimes = [3]*gameTime{ - {Name: "地球平原", NextTime: api.CetusCycle.Expiry.Local(), Status: api.CetusCycle.IsDay, StatusTrueDes: "白天", StatusFalseDes: "夜晚", DayTime: 100 * 60, NightTime: 50 * 60}, - {Name: "金星平原", NextTime: api.VallisCycle.Expiry.Local(), Status: api.VallisCycle.IsWarm, StatusTrueDes: "温暖", StatusFalseDes: "寒冷", DayTime: 400, NightTime: 20 * 60}, - {Name: "火卫二平原", NextTime: api.CambionCycle.Expiry.Local(), Status: api.CambionCycle.Active == "fass", StatusTrueDes: "fass", StatusFalseDes: "vome", DayTime: 100 * 60, NightTime: 50 * 60}, - } -} - -// timeDet游戏时间更新 -func timeDet() { - for _, v := range gameTimes { - // 当前时间对比下一次游戏状态更新时间,看看还剩多少秒 - nt := time.Until(v.NextTime).Seconds() - // 已经过了游戏时间状态更新时间 - if nt < 0 { - v.rwm.Lock() - // 更新游戏状态,如果是白天就切换到晚上,反之亦然 - if v.Status { - // 计算下次的晚上更新时间 - v.NextTime = v.NextTime.Add(time.Duration(v.NightTime) * time.Second) - } else { - // 计算下次的白天更新时间 - v.NextTime = v.NextTime.Add(time.Duration(v.DayTime) * time.Second) - } - v.rwm.Unlock() - } - } -} diff --git a/plugin/warframeapi/main.go b/plugin/warframeapi/main.go index 029eccdf..5ade58cd 100644 --- a/plugin/warframeapi/main.go +++ b/plugin/warframeapi/main.go @@ -1,120 +1,92 @@ -// Package warframeapi 百度内容审核 +// Package warframeapi 星际战甲 package warframeapi import ( - "encoding/json" "fmt" - "net/http" - "sort" "strconv" - "strings" - "sync" "time" - "github.com/FloatTech/floatbox/binary" - "github.com/FloatTech/floatbox/web" ctrl "github.com/FloatTech/zbpctrl" "github.com/FloatTech/zbputils/control" - "github.com/FloatTech/zbputils/img/text" + "github.com/FloatTech/zbputils/ctxext" "github.com/lithammer/fuzzysearch/fuzzy" zero "github.com/wdvxdr1123/ZeroBot" "github.com/wdvxdr1123/ZeroBot/message" ) -var ( - wmitems map[string]items // WarFrame市场的中文名称对应的物品的字典 - itmeNames []string // 物品名称列表 - rt runtime -) - -// 时间同步状态 -type runtime struct { - rwm sync.RWMutex - enable bool // 是否启动 -} - -const wfapiurl = "https://api.warframestat.us/pc" // 星际战甲API -const wfitemurl = "https://api.warframe.market/v1/items" // 星际战甲游戏品信息列表URL +var wmitems, itemNames = newwm() func init() { eng := control.Register("warframeapi", &ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, - Help: "warframeapi\n" + - "- wf时间同步\n" + + Brief: "星际战甲", + Help: "- wf时间同步\n" + "- [金星|地球|火卫二]平原时间\n" + "- .wm [物品名称]\n" + - "- 仲裁\n" + - "- 警报\n" + - "- 每日特惠", + "- wf仲裁\n" + + "- wf警报\n" + + "- wf每日特惠", PrivateDataFolder: "warframeapi", }) - updateWM() - // 获取具体的平原时间,在触发后,会启动持续时间按5分钟的时间更新模拟,以此处理短时间内请求时,时间不会变化的问题 + // 获取具体的平原时间, 在触发后, 会启动持续时间按5分钟的时间更新模拟, 以此处理短时间内请求时, 时间不会变化的问题 eng.OnSuffix("平原时间").SetBlock(true). Handle(func(ctx *zero.Ctx) { - if !rt.enable { // 没有进行同步,就拉取一次服务器状态 - wfapi, err := wfapiGetData() + if !gameWorld.hasSync() { // 没有进行同步,就拉取一次服务器状态 + wfapi, err := newwfapi() if err != nil { - ctx.SendChain(message.Text("Error:获取服务器时间失败")) + ctx.SendChain(message.Text("ERROR: 获取服务器时间失败")) } - loadTime(wfapi) + gameWorld.refresh(&wfapi) } + var msg any switch ctx.State["args"].(string) { case "地球", "夜灵": - ctx.SendChain(message.Text(gameTimes[0])) + msg = gameWorld.w[0] case "金星", "奥布山谷": - ctx.SendChain(message.Text(gameTimes[1])) + msg = gameWorld.w[1] case "魔胎之境", "火卫二", "火卫": - ctx.SendChain(message.Text(gameTimes[2])) + msg = gameWorld.w[2] default: - ctx.SendChain(message.Text("ERROR: 平原不存在")) + msg = "ERROR: 平原不存在" } + ctx.SendChain(message.Text(msg)) // 是否正在进行同步,没有就开启同步,有就不开启 - if !rt.enable { - // 设置标志位 - rt.rwm.Lock() - if rt.enable { // 预检测,防止其他线程同时进来 - return + if !gameWorld.hasSync() { + if gameWorld.setsync() { + go func() { + // 30*10=300=5分钟 + for i := 0; i < 30; i++ { + time.Sleep(10 * time.Second) + gameWorld.update() // 5分钟内每隔10秒更新一下时间 + } + // 5分钟时间同步结束 + _ = gameWorld.resetsync() + }() } - rt.enable = true - rt.rwm.Unlock() - - go func() { - // 30*10=300=5分钟 - for i := 0; i < 30; i++ { - time.Sleep(10 * time.Second) - timeDet() // 5分钟内每隔10秒更新一下时间 - } - // 5分钟时间同步结束 - rt.rwm.Lock() - rt.enable = false - rt.rwm.Unlock() - }() } }) - eng.OnFullMatch("警报").SetBlock(true). + eng.OnFullMatch("wf警报").SetBlock(true). Handle(func(ctx *zero.Ctx) { - wfapi, err := wfapiGetData() + wfapi, err := newwfapi() if err != nil { - ctx.SendChain(message.Text("ERROR:", err.Error())) + ctx.SendChain(message.Text("ERROR: ", err)) return } - // 如果返回的wfapi中,警报数量>0 + // 如果返回的wfapi中, 警报数量>0 if len(wfapi.Alerts) > 0 { - // 遍历警报数据,打印警报信息 - for _, v := range wfapi.Alerts { - // 如果警报处于激活状态 - if v.Active { - ctx.SendChain(stringArrayToImage([]string{ - "节点:" + v.Mission.Node, - "类型:" + v.Mission.Type, - "敌人Lv:" + fmt.Sprint(v.Mission.MinEnemyLevel) + "~" + fmt.Sprint(v.Mission.MaxEnemyLevel), - "奖励:" + v.Mission.Reward.AsString, - "剩余时间:" + v.Eta, - })) - } + msgs := make(message.Message, len(wfapi.Alerts)) + // 遍历警报数据, 打印警报信息 + for i, v := range wfapi.Alerts { + msgs[i] = ctxext.FakeSenderForwardNode(ctx, message.Text( + "激活: ", v.Active, + "\n节点: ", v.Mission.Node, + "\n类型: ", v.Mission.Type, + "\n敌人等级: ", v.Mission.MinEnemyLevel, "~", v.Mission.MaxEnemyLevel, + "\n奖励: ", v.Mission.Reward.AsString, + "\n剩余时间:", v.Eta)) } + ctx.SendChain(msgs...) } }) //TODO:订阅功能-等待重做 @@ -203,38 +175,39 @@ func init() { // ctx.SendChain(msg...) // } // }) - eng.OnFullMatch("仲裁").SetBlock(true). + eng.OnFullMatch("wf仲裁").SetBlock(true). Handle(func(ctx *zero.Ctx) { // 通过wfapi获取仲裁信息 - wfapi, err := wfapiGetData() + wfapi, err := newwfapi() if err != nil { - ctx.SendChain(message.Text("ERROR:", err.Error())) + ctx.SendChain(message.Text("ERROR: ", err)) return } - ctx.SendChain(stringArrayToImage([]string{ - "节点:" + wfapi.Arbitration.Node, - "类型:" + wfapi.Arbitration.Type, - "阵营:" + wfapi.Arbitration.Enemy, - "剩余时间:" + fmt.Sprint(int(wfapi.Arbitration.Expiry.Sub(time.Now().UTC()).Minutes())) + "m", - })) + ctx.SendChain(message.Text( + "节点: ", wfapi.Arbitration.Node, + "\n类型: ", wfapi.Arbitration.Type, + "\n阵营: ", wfapi.Arbitration.Enemy, + "\n剩余时间: ", int(wfapi.Arbitration.Expiry.Sub(time.Now().UTC()).Minutes()), "m", + )) }) - eng.OnFullMatch("每日特惠").SetBlock(true). + eng.OnFullMatch("wf每日特惠").SetBlock(true). Handle(func(ctx *zero.Ctx) { - wfapi, err := wfapiGetData() - + wfapi, err := newwfapi() if err != nil { - ctx.SendChain(message.Text("ERROR:", err.Error())) + ctx.SendChain(message.Text("ERROR: ", err)) return } - for _, dd := range wfapi.DailyDeals { - ctx.SendChain( - message.Text( - "物品:", dd.Item, "\n", - "价格:", dd.OriginalPrice, "→", dd.SalePrice, "\n", - "数量:(", dd.Total, "/", dd.Sold, ")\n", - "时间:", dd.Eta, - ), - ) + if len(wfapi.DailyDeals) > 0 { + msgs := make(message.Message, len(wfapi.DailyDeals)) + for i, dd := range wfapi.DailyDeals { + msgs[i] = ctxext.FakeSenderForwardNode(ctx, message.Text( + "物品: ", dd.Item, + "\n价格: ", dd.OriginalPrice, "→", dd.SalePrice, + "\n数量: (", dd.Total, "/", dd.Sold, ")", + "\n时间: ", dd.Eta, + )) + } + ctx.SendChain(msgs...) } }) // eng.OnRegex(`^入侵$`).SetBlock(true). @@ -251,24 +224,23 @@ func init() { // }) eng.OnFullMatch("wf时间同步").SetBlock(true). Handle(func(ctx *zero.Ctx) { - wfapi, err := wfapiGetData() + wfapi, err := newwfapi() if err != nil { - ctx.SendChain(message.Text("ERROR:", err.Error())) + ctx.SendChain(message.Text("ERROR: ", err)) return } - loadTime(wfapi) + gameWorld.refresh(&wfapi) ctx.SendChain(message.Text("已拉取服务器时间并同步到本地模拟")) }) // 根据名称从Warframe市场查询物品售价 eng.OnPrefix(".wm ").SetBlock(true). Handle(func(ctx *zero.Ctx) { - // 根据输入的名称,从游戏物品名称列表中进行模糊搜索 - sol := fuzzy.FindNormalizedFold(ctx.State["args"].(string), itmeNames) - var msg []string + // 根据输入的名称, 从游戏物品名称列表中进行模糊搜索 + sol := fuzzy.FindNormalizedFold(ctx.State["args"].(string), itemNames) // 物品名称 var name string - // 根据搜搜结果,打印找到的物品 + // 根据搜搜结果, 打印找到的物品 switch len(sol) { case 0: // 没有搜索到任何东西 ctx.SendChain(message.Text("无法查询到该物品")) @@ -276,227 +248,142 @@ func init() { case 1: // 如果只搜索到了一个 name = sol[0] default: // 如果搜搜到了多个 - // 遍历搜索结果,并打印为图片展出 + // 遍历搜索结果, 并打印为图片展出 + msgs := make(message.Message, len(sol)+1) + msgs[0] = ctxext.FakeSenderForwardNode(ctx, message.Text("包含多个结果, 请输入编号查看(15s内),输入c直接结束会话")) for i, v := range sol { - msg = append(msg, fmt.Sprintf("[%d] %s", i, v)) + msgs[i+1] = ctxext.FakeSenderForwardNode(ctx, message.Text("[", i, "] ", v)) } - msg = append(msg, "包含多个结果,请输入编号查看(15s内),输入c直接结束会话") - ctx.SendChain(stringArrayToImage(msg)) - msg = []string{} - - itemIndex := itemNameFutureEvent(ctx, 2) - if itemIndex == -1 { + ctx.SendChain(msgs...) + itemIndex := getitemnameindex(ctx) + if itemIndex < 0 { + return + } + if itemIndex >= len(sol) || itemIndex < 0 { + ctx.SendChain(message.Text("ERROR: 编号超出范围")) return } name = sol[itemIndex] } - Mf := false + onlymaxrank := false + msgs := message.Message{} GETWM: - if Mf { - msg = []string{} + if onlymaxrank { + msgs = msgs[:0] } - sells, itmeinfo, txt, err := wmItemOrders(wmitems[name].URLName, Mf) - if !Mf { - if itmeinfo.ZhHans.WikiLink == "" { - ctx.Send([]message.MessageSegment{ - message.Image("https://warframe.market/static/assets/" + wmitems[name].Thumb), - message.Text(wmitems[name].ItemName, "\n"), - }) + sells, iteminfo, txt, err := getitemsorder(wmitems[name].URLName, onlymaxrank) + if !onlymaxrank { + if iteminfo.ZhHans.WikiLink == "" { + msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx, + message.Image("https://warframe.market/static/assets/"+wmitems[name].Thumb), + message.Text("\n", wmitems[name].ItemName))) } else { - ctx.Send([]message.MessageSegment{ - message.Image("https://warframe.market/static/assets/" + wmitems[name].Thumb), - message.Text(wmitems[name].ItemName, "\n"), - message.Text("wiki:", itmeinfo.ZhHans.WikiLink), - }) + msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx, + message.Image("https://warframe.market/static/assets/"+wmitems[name].Thumb), + message.Text("\n", wmitems[name].ItemName, "\nwiki: ", iteminfo.ZhHans.WikiLink))) } } - msg = append(msg, wmitems[name].ItemName) - if err != nil { - ctx.Send(message.Text("Error:", err.Error())) + msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx, message.Text("ERROR: ", err))) + ctx.SendChain(msgs...) return } if sells == nil { - ctx.Send(message.Text("无可购买对象")) + msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx, message.Text("无可购买对象"))) + ctx.SendChain(msgs...) return } - ismod := false - if itmeinfo.ModMaxRank != 0 { - ismod = true - } - + ismod := iteminfo.ModMaxRank != 0 max := 5 - if len(sells) <= max { + if len(sells) < max { max = len(sells) } - for i := 0; i < max; i++ { - if ismod { - msg = append(msg, fmt.Sprintf("[%d](Rank:%d/%d) %dP - %s\n", i, sells[i].ModRank, itmeinfo.ModMaxRank, sells[i].Platinum, sells[i].User.IngameName)) + if ismod { + if !onlymaxrank { + msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx, message.Text("请输入编号选择, 或输入r获取满级报价(30s内)\n输入c直接结束会话"))) } else { - msg = append(msg, fmt.Sprintf("[%d] %dP -%s\n", i, sells[i].Platinum, sells[i].User.IngameName)) + msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx, message.Text("请输入编号选择(30s内)\n输入c直接结束会话"))) + } + for i := 0; i < max; i++ { + msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx, + message.Text(fmt.Sprintf("[%d] (Rank:%d/%d) %dP - %s\n", i, sells[i].ModRank, iteminfo.ModMaxRank, sells[i].Platinum, sells[i].User.IngameName)))) } - } - - if ismod && !Mf { - msg = append(msg, "请输入编号选择,或输入r获取满级报价(30s内)\n输入c直接结束会话") } else { - msg = append(msg, "请输入编号选择(30s内)\n输入c直接结束会话") - } - ctx.SendChain(stringArrayToImage(msg)) - - GETNUM3: - next := zero.NewFutureEvent("message", 999, false, ctx.CheckSession()).Next() - select { - case <-time.After(time.Second * 30): - ctx.SendChain(message.Text("会话已结束!")) - return - case e := <-next: - msg := e.Event.Message.ExtractPlainText() - // 重新获取报价 - if msg == "r" { - Mf = true - goto GETWM + for i := 0; i < max; i++ { + msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx, + message.Text(fmt.Sprintf("[%d] %dP -%s\n", i, sells[i].Platinum, sells[i].User.IngameName)))) } - // 主动结束会话 - if msg == "c" { + } + ctx.SendChain(msgs...) + + for i := 0; i < 3; i++ { + next := zero.NewFutureEvent("message", 999, false, ctx.CheckSession()).Next() + select { + case <-time.After(time.Second * 30): ctx.SendChain(message.Text("会话已结束!")) return - } - i, err := strconv.Atoi(msg) - if err != nil { - ctx.SendChain(message.Text("请输入数字!(输入c结束会话)")) - goto GETNUM3 - } - if err == nil { - if ismod { - ctx.Send(message.Text("/w ", sells[i].User.IngameName, " Hi! I want to buy: ", txt, "(Rank:", sells[i].ModRank, ") for ", sells[i].Platinum, " platinum. (warframe.market)")) - } else { - ctx.Send(message.Text("/w ", sells[i].User.IngameName, " Hi! I want to buy: ", txt, " for ", sells[i].Platinum, " platinum. (warframe.market)")) + case e := <-next: + msg := e.Event.Message.ExtractPlainText() + // 重新获取报价 + if msg == "r" { + onlymaxrank = true + goto GETWM } + // 主动结束会话 + if msg == "c" { + ctx.SendChain(message.Text("会话已结束!")) + return + } + i, err := strconv.Atoi(msg) + if err != nil { + ctx.SendChain(message.Text("请输入数字! (输入c结束会话)")) + continue + } + if ismod { + ctx.SendChain(message.Text("/w ", sells[i].User.IngameName, " Hi! I want to buy: ", txt, "(Rank:", sells[i].ModRank, ") for ", sells[i].Platinum, " platinum. (warframe.market)")) + } else { + ctx.SendChain(message.Text("/w ", sells[i].User.IngameName, " Hi! I want to buy: ", txt, " for ", sells[i].Platinum, " platinum. (warframe.market)")) + } + return } } }) } -// 获取搜索结果中的物品具体名称index的FutureEvent,传入ctx和一个递归次数上限,返回一个int,如果为返回内容为-1,说明会话超时,或主动结束,或超出递归 -func itemNameFutureEvent(ctx *zero.Ctx, count int) int { - next := zero.NewFutureEvent("message", 999, false, ctx.CheckSession()).Next() - select { - case <-time.After(time.Second * 15): - // 超时15秒处理 - ctx.SendChain(message.Text("会话已超时!")) - return -1 - case e := <-next: - msg := e.Event.Message.ExtractPlainText() - // 输入c主动结束的处理 - if msg == "c" { - ctx.SendChain(message.Text("会话已结束!")) +// 获取搜索结果中的物品具体名称index的FutureEvent +// +// 传入ctx和一个递归次数上限,返回一个int +// 如果为返回内容为负, 说明 +// -1 会话超时 +// -2 主动结束 +// -3 连续3次错误 +func getitemnameindex(ctx *zero.Ctx) int { + recv, cancel := zero.NewFutureEvent("message", 999, false, ctx.CheckSession()).Repeat() + defer cancel() + for i := 0; i < 3; i++ { + select { + case <-time.After(time.Second * 15): + // 超时15秒处理 + ctx.SendChain(message.Text("会话已超时!")) return -1 - } - // 尝试对输入进行数字转换 - num, err := strconv.Atoi(msg) - // 如果出错,说明输入的并非数字,则重新触发该内容 - if err != nil { - // 查看是否超时 - if count == 0 { - ctx.SendChain(message.Text("连续输入错误,会话已结束!")) - return -1 + case e := <-recv: + msg := e.Event.Message.ExtractPlainText() + // 输入c主动结束的处理 + if msg == "c" { + ctx.SendChain(message.Text("会话已结束!")) + return -2 } - ctx.SendChain(message.Text("请输入数字!(输入c结束会话)[", count, "]")) - count-- - return itemNameFutureEvent(ctx, count) - } - return num - } -} - -// 数组字符串转图片 -func stringArrayToImage(texts []string) message.MessageSegment { - b, err := text.RenderToBase64(strings.Join(texts, "\n"), text.FontFile, 400, 20) - if err != nil { - return message.Text("ERROR: ", err) - } - return message.Image("base64://" + binary.BytesToString(b)) -} - -// 从WFapi获取数据 -func wfapiGetData() (wfAPI, error) { - var wfapi wfAPI // WarFrameAPI的数据实例 - var data []byte - var err error - data, err = web.GetData(wfapiurl) - if err != nil { - return wfapi, err - } - err = json.Unmarshal(data, &wfapi) - if err != nil { - return wfapi, err - } - return wfapi, nil -} - -// 从WF市场获取物品数据信息 -func updateWM() { - var itmeapi wfAPIItem // WarFrame市场的数据实例 - - data, err := web.RequestDataWithHeaders(&http.Client{}, wfitemurl, "GET", func(request *http.Request) error { - request.Header.Add("Accept", "application/json") - request.Header.Add("Language", "zh-hans") - return nil - }, nil) - if err != nil { - panic(err) - } - err = json.Unmarshal(data, &itmeapi) - if err != nil { - panic(err) - } - loadToFuzzy(itmeapi) -} - -// 获取Warframe市场的售价表,并进行排序,cn_name为物品中文名称,onlyMaxRank表示只取最高等级的物品,返回物品售价表,物品信息,物品英文 -func wmItemOrders(cnName string, onlyMaxRank bool) (orders, itemsInSet, string, error) { - var wfapiio wfAPIItemsOrders - data, err := web.RequestDataWithHeaders(&http.Client{}, fmt.Sprintf("https://api.warframe.market/v1/items/%s/orders?include=item", cnName), "GET", func(request *http.Request) error { - request.Header.Add("Accept", "application/json") - request.Header.Add("Platform", "pc") - return nil - }, nil) - if err != nil { - return nil, itemsInSet{}, "", err - } - err = json.Unmarshal(data, &wfapiio) - var sellOrders orders - // 遍历市场物品列表 - for _, v := range wfapiio.Payload.Orders { - // 取其中类型为售卖,且去掉不在线的玩家 - if v.OrderType == "sell" && v.User.Status != "offline" { - // 如果需要满级报价 - if onlyMaxRank && v.ModRank == wfapiio.Include.Item.ItemsInSet[0].ModMaxRank { - sellOrders = append(sellOrders, v) - } else if !onlyMaxRank { - sellOrders = append(sellOrders, v) + // 尝试对输入进行数字转换 + num, err := strconv.Atoi(msg) + if err != nil { + ctx.SendChain(message.Text("请输入数字! (输入c结束会话)")) + continue } + return num } } - // 对报价表进行排序,由低到高 - sort.Sort(sellOrders) - // 获取物品信息 - for i, v := range wfapiio.Include.Item.ItemsInSet { - if v.URLName == cnName { - return sellOrders, wfapiio.Include.Item.ItemsInSet[i], wfapiio.Include.Item.ItemsInSet[i].En.ItemName, err - } - } - return sellOrders, wfapiio.Include.Item.ItemsInSet[0], wfapiio.Include.Item.ItemsInSet[0].En.ItemName, err -} - -func loadToFuzzy(wminfo wfAPIItem) { - wmitems = make(map[string]items) - itmeNames = []string{} - for _, v := range wminfo.Payload.Items { - wmitems[v.ItemName] = v - itmeNames = append(itmeNames, v.ItemName) - } + ctx.SendChain(message.Text("连续输入错误, 会话已结束!")) + return -3 } diff --git a/plugin/warframeapi/wfdata.go b/plugin/warframeapi/types.go similarity index 99% rename from plugin/warframeapi/wfdata.go rename to plugin/warframeapi/types.go index 89c822d5..693d0ea8 100644 --- a/plugin/warframeapi/wfdata.go +++ b/plugin/warframeapi/types.go @@ -2,7 +2,7 @@ package warframeapi import "time" -type wfAPI struct { +type wfapi struct { Timestamp time.Time `json:"timestamp"` News []news `json:"news"` Events []events `json:"events"` diff --git a/plugin/warframeapi/world.go b/plugin/warframeapi/world.go new file mode 100644 index 00000000..f7280040 --- /dev/null +++ b/plugin/warframeapi/world.go @@ -0,0 +1,99 @@ +package warframeapi + +import ( + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/davidscholberg/go-durationfmt" +) + +// 游戏时间模拟 +type timezone struct { + sync.RWMutex `json:"-"` + Name string `json:"name"` // 时间名称 + NextTime time.Time `json:"time"` // 下次更新时间 + IsDay bool `json:"status"` // 状态 + hasSync bool `json:"-"` // 是否已同步 + DayDesc string `json:"true_des"` // 状态说明 + NightDesc string `json:"false_des"` // 状态说明 + DayLen int `json:"day"` // 白天时长 + NightLen int `json:"night"` // 夜间时长 +} + +type world struct { + w [3]*timezone + hassync uintptr +} + +var ( + gameWorld world +) + +// String 根据传入的世界编号,获取对应的游戏时间文本 +func (t *timezone) String() string { + t.RLock() + defer t.RUnlock() + sb := strings.Builder{} + sb.WriteString("平原时间: ") + if t.IsDay { + sb.WriteString(t.DayDesc) + } else { + sb.WriteString(t.NightDesc) + } + sb.WriteString(", ") + sb.WriteString("下次更新: ") + d := time.Until(t.NextTime) + durStr, _ := durationfmt.Format(d, "%m分%s秒后") + sb.WriteString(durStr) + return sb.String() +} + +func (w *world) hasSync() bool { + return atomic.LoadUintptr(&w.hassync) != 0 +} + +func (w *world) setsync() bool { + return atomic.CompareAndSwapUintptr(&w.hassync, 0, 1) +} + +func (w *world) resetsync() bool { + return atomic.CompareAndSwapUintptr(&w.hassync, 1, 0) +} + +// 根据API返回内容修正游戏时间 +func (w *world) refresh(api *wfapi) { + for _, t := range w.w { + t.Lock() + } + w.w = [3]*timezone{ + {Name: "地球平原", NextTime: api.CetusCycle.Expiry.Local(), IsDay: api.CetusCycle.IsDay, DayDesc: "白天", NightDesc: "夜晚", DayLen: 100 * 60, NightLen: 50 * 60}, + {Name: "金星平原", NextTime: api.VallisCycle.Expiry.Local(), IsDay: api.VallisCycle.IsWarm, DayDesc: "温暖", NightDesc: "寒冷", DayLen: 400, NightLen: 20 * 60}, + {Name: "火卫二平原", NextTime: api.CambionCycle.Expiry.Local(), IsDay: api.CambionCycle.Active == "fass", DayDesc: "fass", NightDesc: "vome", DayLen: 100 * 60, NightLen: 50 * 60}, + } +} + +// 游戏时间更新 +func (w *world) update() { + if !w.hasSync() { + return + } + for _, t := range w.w { + t.Lock() + // 当前时间对比下一次游戏状态更新时间,看看还剩多少秒 + nt := time.Until(t.NextTime).Seconds() + // 已经过了游戏时间状态更新时间 + if nt < 0 { + // 更新游戏状态,如果是白天就切换到晚上,反之亦然 + if t.IsDay { + // 计算下次的晚上更新时间 + t.NextTime = t.NextTime.Add(time.Duration(t.NightLen) * time.Second) + } else { + // 计算下次的白天更新时间 + t.NextTime = t.NextTime.Add(time.Duration(t.DayLen) * time.Second) + } + } + t.Unlock() + } +}