diff --git a/README.md b/README.md index a8a17754af..b283fd7360 100644 --- a/README.md +++ b/README.md @@ -1289,6 +1289,23 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 来份网易云热评 + +
+ WarframeAPI + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/warframeapi"` + + - [x] wf时间同步 + + - [x] [金星|地球|火卫二]平原状态 + + - [x] .wm [物品名称] + + - [x] 仲裁 + + - [x] 警报 + + - [x] 每日特惠
天气/拼音查询-名言 diff --git a/go.mod b/go.mod index a1b74c8565..42d9b4cd56 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 github.com/antchfx/htmlquery v1.2.5 github.com/corona10/goimagehash v1.1.0 + github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3 github.com/fumiama/ahsai v0.1.0 github.com/fumiama/cron v1.3.0 github.com/fumiama/go-base16384 v1.6.4 @@ -25,6 +26,7 @@ require ( github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/jinzhu/gorm v1.9.16 github.com/jozsefsallai/gophersauce v1.0.1 + github.com/lithammer/fuzzysearch v1.1.5 github.com/lucas-clemente/quic-go v0.31.1 github.com/mroth/weightedrand v1.0.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 6bd88e83da..baf20eef3b 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozb github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3 h1:qshMBFxVjYjzI+kwvWvgoByF3uMCvnJiaK8KslWAbr8= +github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3/go.mod h1:M9fx6rAdHSYLKxXPgUXGgblb586CA7ceNrpu4DEc2No= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= @@ -126,6 +128,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= +github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= github.com/lucas-clemente/quic-go v0.31.1 h1:O8Od7hfioqq0PMYHDyBkxU2aA7iZ2W9pjbrWuja2YR4= github.com/lucas-clemente/quic-go v0.31.1/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= diff --git a/main.go b/main.go index a3be4c50a4..012bcd174d 100644 --- a/main.go +++ b/main.go @@ -132,6 +132,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/vtb_quotation" // vtb语录 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wallet" // 钱包 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wangyiyun" // 网易云音乐热评 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/warframeapi" // warframeAPI插件 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wenben" // 文本指令大全 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wenxinAI" // 百度文心AI画图 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/word_count" // 聊天热词 @@ -261,7 +262,6 @@ func init() { logrus.Infoln("[main] 从", *runcfg, "读取配置文件") return } - config.W = []*driver.WSClient{driver.NewWebSocketClient(*url, *token)} config.Z = zero.Config{ NickName: append([]string{*adana}, "ATRI", "atri", "亚托莉", "アトリ"), diff --git a/plugin/warframeapi/gametime.go b/plugin/warframeapi/gametime.go new file mode 100644 index 0000000000..c62f0bd190 --- /dev/null +++ b/plugin/warframeapi/gametime.go @@ -0,0 +1,78 @@ +package warframeapi + +import ( + "github.com/davidscholberg/go-durationfmt" + "sync" + "time" +) + +// 游戏时间模拟 +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 new file mode 100644 index 0000000000..913cb98806 --- /dev/null +++ b/plugin/warframeapi/main.go @@ -0,0 +1,507 @@ +// Package warframeapi 百度内容审核 +package warframeapi + +import ( + "encoding/json" + "fmt" + "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/lithammer/fuzzysearch/fuzzy" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" + "net/http" + "sort" + "strconv" + "strings" + "sync" + "time" +) + +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 + +func init() { + eng := control.Register("warframeapi", &ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Help: "warframeapi\n" + + "- wf时间同步\n" + + "- [金星|地球|火卫二]平原时间\n" + + "- .wm [物品名称]\n" + + "- 仲裁\n" + + "- 警报\n" + + "- 每日特惠", + PrivateDataFolder: "warframeapi", + }) + updateWM() + + //获取具体的平原时间,在触发后,会启动持续时间按5分钟的时间更新模拟,以此处理短时间内请求时,时间不会变化的问题 + eng.OnSuffix("平原时间").SetBlock(true). + Handle(func(ctx *zero.Ctx) { + if !rt.enable { //没有进行同步,就拉取一次服务器状态 + wfapi, err := wfapiGetData() + if err != nil { + ctx.SendChain(message.Text("Error:获取服务器时间失败")) + } + loadTime(wfapi) + } + switch ctx.State["args"].(string) { + case "地球", "夜灵": + ctx.SendChain(message.Text(gameTimes[0])) + case "金星", "奥布山谷": + ctx.SendChain(message.Text(gameTimes[1])) + case "魔胎之境", "火卫二", "火卫": + ctx.SendChain(message.Text(gameTimes[2])) + default: + ctx.SendChain(message.Text("ERROR: 平原不存在")) + } + // 是否正在进行同步,没有就开启同步,有就不开启 + if !rt.enable { + // 设置标志位 + rt.rwm.Lock() + if rt.enable { //预检测,防止其他线程同时进来 + return + } + 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). + Handle(func(ctx *zero.Ctx) { + wfapi, err := wfapiGetData() + if err != nil { + ctx.SendChain(message.Text("ERROR:", err.Error())) + return + } + //如果返回的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, + })) + + } + } + } + + }) + //TODO:订阅功能-等待重做 + //eng.OnRegex(`^(订阅|取消订阅)(.*)平原(.*)$`).SetBlock(true). + // Handle(func(ctx *zero.Ctx) { + // args := ctx.State["regex_matched"].([]string) + // var isEnable bool + // if args[1] == "订阅" { + // isEnable = true + // } + // updateWFAPI() + // status := false + // switch args[3] { + // case "fass", "白天", "温暖": + // status = true + // } + // switch args[2] { + // case "金星", "奥布山谷": + // //sublist = append(sublist, subList{ctx.Event.GroupID, ctx.Event.UserID, 1, status, false}) + // if isEnable { + // addUseSub(ctx.Event.UserID, ctx.Event.GroupID, 1, status) + // } else { + // removeUseSub(ctx.Event.UserID, ctx.Event.GroupID, 1) + // } + // ctx.SendChain( + // message.At(ctx.Event.UserID), + // message.Text("已成功", args[1]), + // message.Text(gameTimes[1].Name), + // message.Text(status), + // ) + // case "地球", "夜灵": + // if isEnable { + // addUseSub(ctx.Event.UserID, ctx.Event.GroupID, 0, status) + // } else { + // removeUseSub(ctx.Event.UserID, ctx.Event.GroupID, 0) + // } + // ctx.SendChain( + // message.At(ctx.Event.UserID), + // message.Text("已成功", args[1]), + // message.Text(gameTimes[0].Name), + // message.Text(status), + // ) + // case "魔胎之境", "火卫", "火卫二": + // if isEnable { + // addUseSub(ctx.Event.UserID, ctx.Event.GroupID, 2, status) + // } else { + // removeUseSub(ctx.Event.UserID, ctx.Event.GroupID, 2) + // } + // ctx.SendChain( + // message.At(ctx.Event.UserID), + // message.Text("已成功", args[1]), + // message.Text(gameTimes[2].Name), + // message.Text(status), + // ) + // default: + // ctx.SendChain(message.Text("ERROR: 平原不存在")) + // return + // } + // }) + //eng.OnFullMatch(`wf订阅检测`).SetBlock(true).Handle(func(ctx *zero.Ctx) { + // rwm.Lock() + // var msg []message.MessageSegment + // for i, v := range gameTimes { + // nt := time.Until(v.NextTime).Seconds() + // switch { + // case nt < 0: + // 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.Status = !v.Status + // + // msg = callUser(i, v.Status, 0) + // case nt < float64(5)*60: + // msg = callUser(i, !v.Status, 5) + // case nt < float64(15)*60: + // if i == 2 && !v.Status { + // return + // } + // msg = callUser(i, !v.Status, 15) + // } + // } + // rwm.Unlock() + // if msg != nil && len(msg) > 0 { + // ctx.SendChain(msg...) + // } + //}) + eng.OnFullMatch("仲裁").SetBlock(true). + Handle(func(ctx *zero.Ctx) { + //通过wfapi获取仲裁信息 + wfapi, err := wfapiGetData() + if err != nil { + ctx.SendChain(message.Text("ERROR:", err.Error())) + 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", + })) + }) + eng.OnFullMatch("每日特惠").SetBlock(true). + Handle(func(ctx *zero.Ctx) { + wfapi, err := wfapiGetData() + + if err != nil { + ctx.SendChain(message.Text("ERROR:", err.Error())) + 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, + ), + ) + } + }) + // eng.OnRegex(`^入侵$`).SetBlock(true). + // Handle(func(ctx *zero.Ctx) { + // updateWFAPI(ctx) + // for _, dd := range wfapi.dailyDeals { + // imagebuild.DrawTextSend([]string{ + // "节点:" + wfapi.arbitration.Node, + // "类型:" + wfapi.arbitration.Type, + // "阵营:" + wfapi.arbitration.Enemy, + // "剩余时间:" + fmt.Sprint(int(wfapi.arbitration.Expiry.Sub(time.Now().UTC()).Minutes())) + "m", + // }, ctx) + // } + // }) + eng.OnFullMatch("wf时间同步").SetBlock(true). + Handle(func(ctx *zero.Ctx) { + wfapi, err := wfapiGetData() + if err != nil { + ctx.SendChain(message.Text("ERROR:", err.Error())) + return + } + loadTime(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 + //物品名称 + var name string + + //根据搜搜结果,打印找到的物品 + switch len(sol) { + case 0: //没有搜索到任何东西 + ctx.SendChain(message.Text("无法查询到该物品")) + return + case 1: //如果只搜索到了一个 + name = sol[0] + default: //如果搜搜到了多个 + //遍历搜索结果,并打印为图片展出 + for i, v := range sol { + msg = append(msg, fmt.Sprintf("[%d] %s", i, v)) + } + msg = append(msg, "包含多个结果,请输入编号查看(15s内),输入c直接结束会话") + ctx.SendChain(stringArrayToImage(msg)) + msg = []string{} + + itemIndex := itemNameFutureEvent(ctx, 2) + if itemIndex == -1 { + return + } + name = sol[itemIndex] + } + Mf := false + GETWM: + if Mf { + msg = []string{} + } + 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"), + }) + } 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), + }) + } + } + + msg = append(msg, wmitems[name].ItemName) + + if err != nil { + ctx.Send(message.Text("Error:", err.Error())) + return + } + if sells == nil { + ctx.Send(message.Text("无可购买对象")) + return + } + + ismod := false + if itmeinfo.ModMaxRank != 0 { + ismod = true + } + + max := 5 + 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)) + } else { + msg = append(msg, fmt.Sprintf("[%d] %dP -%s\n", i, 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 + } + // 主动结束会话 + if msg == "c" { + 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)")) + } + } + } + }) +} + +// 获取搜索结果中的物品具体名称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("会话已结束!")) + return -1 + } + //尝试对输入进行数字转换 + num, err := strconv.Atoi(msg) + //如果出错,说明输入的并非数字,则重新触发该内容 + if err != nil { + //查看是否超时 + if count == 0 { + ctx.SendChain(message.Text("连续输入错误,会话已结束!")) + return -1 + } + 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) + } + + } + } + //对报价表进行排序,由低到高 + 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) + } +} diff --git a/plugin/warframeapi/wfdata.go b/plugin/warframeapi/wfdata.go new file mode 100644 index 0000000000..89c822d5ac --- /dev/null +++ b/plugin/warframeapi/wfdata.go @@ -0,0 +1,630 @@ +package warframeapi + +import "time" + +type wfAPI struct { + Timestamp time.Time `json:"timestamp"` + News []news `json:"news"` + Events []events `json:"events"` + Alerts []alerts `json:"alerts"` + Sortie sortie `json:"sortie"` + SyndicateMissions []syndicateMissions `json:"syndicateMissions"` + Fissures []fissures `json:"fissures"` + GlobalUpgrades []interface{} `json:"globalUpgrades"` + FlashSales []flashSales `json:"flashSales"` + Invasions []invasions `json:"invasions"` + DarkSectors []interface{} `json:"darkSectors"` + VoidTrader voidTrader `json:"voidTrader"` + DailyDeals []dailyDeals `json:"dailyDeals"` + Simaris simaris `json:"simaris"` + ConclaveChallenges []conclaveChallenges `json:"conclaveChallenges"` + PersistentEnemies []interface{} `json:"persistentEnemies"` + EarthCycle earthCycle `json:"earthCycle"` + CetusCycle cetusCycle `json:"cetusCycle"` + CambionCycle cambionCycle `json:"cambionCycle"` + ZarimanCycle zarimanCycle `json:"zarimanCycle"` + WeeklyChallenges []interface{} `json:"weeklyChallenges"` + ConstructionProgress constructionProgress `json:"constructionProgress"` + VallisCycle vallisCycle `json:"vallisCycle"` + Nightwave nightwave `json:"nightwave"` + Kuva []interface{} `json:"kuva"` + Arbitration arbitration `json:"arbitration"` + SentientOutposts sentientOutposts `json:"sentientOutposts"` + SteelPath steelPath `json:"steelPath"` + VaultTrader vaultTrader `json:"vaultTrader"` +} +type translations struct { + En string `json:"en"` + Fr string `json:"fr"` + It string `json:"it"` + De string `json:"de"` + Es string `json:"es"` + Pt string `json:"pt"` + Ru string `json:"ru"` + Pl string `json:"pl"` + Uk string `json:"uk"` + Tr string `json:"tr"` + Ja string `json:"ja"` + Zh string `json:"zh"` + Ko string `json:"ko"` + Tc string `json:"tc"` +} +type news struct { + ID string `json:"id"` + Message string `json:"message"` + Link string `json:"link"` + ImageLink string `json:"imageLink"` + Priority bool `json:"priority"` + Date time.Time `json:"date"` + Eta string `json:"eta"` + Update bool `json:"update"` + PrimeAccess bool `json:"primeAccess"` + Stream bool `json:"stream"` + Translations translations `json:"translations"` + AsString string `json:"asString"` +} +type metadata struct { +} +type nextAlt struct { + Expiry time.Time `json:"expiry"` + Activation time.Time `json:"activation"` +} +type events struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + MaximumScore int `json:"maximumScore"` + CurrentScore int `json:"currentScore"` + SmallInterval interface{} `json:"smallInterval"` + LargeInterval interface{} `json:"largeInterval"` + Faction string `json:"faction"` + Description string `json:"description"` + Tooltip string `json:"tooltip"` + Node string `json:"node"` + ConcurrentNodes []interface{} `json:"concurrentNodes"` + Rewards []interface{} `json:"rewards"` + Expired bool `json:"expired"` + InterimSteps []interface{} `json:"interimSteps"` + ProgressSteps []interface{} `json:"progressSteps"` + IsPersonal bool `json:"isPersonal"` + RegionDrops []interface{} `json:"regionDrops"` + ArchwingDrops []interface{} `json:"archwingDrops"` + AsString string `json:"asString"` + Metadata metadata `json:"metadata"` + CompletionBonuses []interface{} `json:"completionBonuses"` + AltExpiry time.Time `json:"altExpiry"` + AltActivation time.Time `json:"altActivation"` + NextAlt nextAlt `json:"nextAlt"` +} +type variants struct { + MissionType string `json:"missionType"` + Modifier string `json:"modifier"` + ModifierDescription string `json:"modifierDescription"` + Node string `json:"node"` +} +type sortie struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + RewardPool string `json:"rewardPool"` + Variants []variants `json:"variants"` + Boss string `json:"boss"` + Faction string `json:"faction"` + Expired bool `json:"expired"` + Eta string `json:"eta"` +} +type jobs struct { + ID string `json:"id"` + RewardPool []string `json:"rewardPool"` + Type string `json:"type"` + EnemyLevels []int `json:"enemyLevels"` + StandingStages []int `json:"standingStages"` + MinMR int `json:"minMR"` + Expiry time.Time `json:"expiry"` + TimeBound string `json:"timeBound,omitempty"` +} +type syndicateMissions struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + Syndicate string `json:"syndicate"` + SyndicateKey string `json:"syndicateKey"` + Nodes []interface{} `json:"nodes"` + Jobs []jobs `json:"jobs"` + Eta string `json:"eta"` +} +type fissures struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + Node string `json:"node"` + MissionType string `json:"missionType"` + MissionKey string `json:"missionKey"` + Enemy string `json:"enemy"` + EnemyKey string `json:"enemyKey"` + NodeKey string `json:"nodeKey"` + Tier string `json:"tier"` + TierNum int `json:"tierNum"` + Expired bool `json:"expired"` + Eta string `json:"eta"` + IsStorm bool `json:"isStorm"` +} +type flashSales struct { + Item string `json:"item"` + Expiry time.Time `json:"expiry"` + Activation time.Time `json:"activation"` + Discount int `json:"discount"` + RegularOverride int `json:"regularOverride"` + PremiumOverride int `json:"premiumOverride"` + IsShownInMarket bool `json:"isShownInMarket"` + IsFeatured bool `json:"isFeatured"` + IsPopular bool `json:"isPopular"` + ID string `json:"id"` + Expired bool `json:"expired"` + Eta string `json:"eta"` +} +type countedItems struct { + Count int `json:"count"` + Type string `json:"type"` + Key string `json:"key"` +} +type attackerReward struct { + Items []interface{} `json:"items"` + CountedItems []countedItems `json:"countedItems"` + Credits int `json:"credits"` + AsString string `json:"asString"` + ItemString string `json:"itemString"` + Thumbnail string `json:"thumbnail"` + Color int `json:"color"` +} +type reward struct { + Items []interface{} `json:"items"` + CountedItems []countedItems `json:"countedItems"` + Credits int `json:"credits"` + AsString string `json:"asString"` + ItemString string `json:"itemString"` + Thumbnail string `json:"thumbnail"` + Color int `json:"color"` +} +type attacker struct { + Reward reward `json:"reward"` + Faction string `json:"faction"` + FactionKey string `json:"factionKey"` +} +type defenderReward struct { + Items []interface{} `json:"items"` + CountedItems []countedItems `json:"countedItems"` + Credits int `json:"credits"` + AsString string `json:"asString"` + ItemString string `json:"itemString"` + Thumbnail string `json:"thumbnail"` + Color int `json:"color"` +} +type defender struct { + Reward reward `json:"reward"` + Faction string `json:"faction"` + FactionKey string `json:"factionKey"` +} +type invasions struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Node string `json:"node"` + NodeKey string `json:"nodeKey"` + Desc string `json:"desc"` + AttackerReward attackerReward `json:"attackerReward"` + AttackingFaction string `json:"attackingFaction"` + Attacker attacker `json:"attacker"` + DefenderReward defenderReward `json:"defenderReward"` + DefendingFaction string `json:"defendingFaction"` + Defender defender `json:"defender"` + VsInfestation bool `json:"vsInfestation"` + Count int `json:"count"` + RequiredRuns int `json:"requiredRuns"` + Completion float64 `json:"completion"` + Completed bool `json:"completed"` + Eta string `json:"eta"` + RewardTypes []string `json:"rewardTypes"` +} +type voidTrader struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + Character string `json:"character"` + Location string `json:"location"` + Inventory []interface{} `json:"inventory"` + PsID string `json:"psId"` + EndString string `json:"endString"` + InitialStart time.Time `json:"initialStart"` + Schedule []interface{} `json:"schedule"` +} +type dailyDeals struct { + Item string `json:"item"` + Expiry time.Time `json:"expiry"` + Activation time.Time `json:"activation"` + OriginalPrice int `json:"originalPrice"` + SalePrice int `json:"salePrice"` + Total int `json:"total"` + Sold int `json:"sold"` + ID string `json:"id"` + Eta string `json:"eta"` + Discount int `json:"discount"` +} +type simaris struct { + Target string `json:"target"` + IsTargetActive bool `json:"isTargetActive"` + AsString string `json:"asString"` +} +type conclaveChallenges struct { + ID string `json:"id"` + Expiry time.Time `json:"expiry"` + Activation time.Time `json:"activation"` + Amount int `json:"amount"` + Mode string `json:"mode"` + Category string `json:"category"` + Eta string `json:"eta"` + Expired bool `json:"expired"` + Daily bool `json:"daily"` + RootChallenge bool `json:"rootChallenge"` + EndString string `json:"endString"` + Description string `json:"description"` + Title string `json:"title"` + Standing int `json:"standing"` + AsString string `json:"asString"` +} +type earthCycle struct { + ID string `json:"id"` + Expiry time.Time `json:"expiry"` + Activation time.Time `json:"activation"` + IsDay bool `json:"isDay"` + State string `json:"state"` + TimeLeft string `json:"timeLeft"` +} +type cetusCycle struct { + ID string `json:"id"` + Expiry time.Time `json:"expiry"` + Activation time.Time `json:"activation"` + IsDay bool `json:"isDay"` + State string `json:"state"` + TimeLeft string `json:"timeLeft"` + IsCetus bool `json:"isCetus"` + ShortString string `json:"shortString"` +} +type cambionCycle struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + Expiry time.Time `json:"expiry"` + TimeLeft string `json:"timeLeft"` + Active string `json:"active"` +} +type zarimanCycle struct { + ID string `json:"id"` + BountiesEndDate time.Time `json:"bountiesEndDate"` + Expiry time.Time `json:"expiry"` + Activation time.Time `json:"activation"` + IsCorpus bool `json:"isCorpus"` + State string `json:"state"` + TimeLeft string `json:"timeLeft"` + ShortString string `json:"shortString"` +} +type constructionProgress struct { + ID string `json:"id"` + FomorianProgress string `json:"fomorianProgress"` + RazorbackProgress string `json:"razorbackProgress"` + UnknownProgress string `json:"unknownProgress"` +} +type vallisCycle struct { + ID string `json:"id"` + Expiry time.Time `json:"expiry"` + IsWarm bool `json:"isWarm"` + State string `json:"state"` + Activation time.Time `json:"activation"` + TimeLeft string `json:"timeLeft"` + ShortString string `json:"shortString"` +} +type params struct { +} +type activeChallenges struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + IsDaily bool `json:"isDaily,omitempty"` + IsElite bool `json:"isElite"` + Desc string `json:"desc"` + Title string `json:"title"` + Reputation int `json:"reputation"` +} +type nightwave struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + Season int `json:"season"` + Tag string `json:"tag"` + Phase int `json:"phase"` + Params params `json:"params"` + PossibleChallenges []interface{} `json:"possibleChallenges"` + ActiveChallenges []activeChallenges `json:"activeChallenges"` + RewardTypes []string `json:"rewardTypes"` +} +type arbitration struct { + Activation time.Time `json:"activation"` + Expiry time.Time `json:"expiry"` + Enemy string `json:"enemy"` + Type string `json:"type"` + Archwing bool `json:"archwing"` + Sharkwing bool `json:"sharkwing"` + Node string `json:"node"` + NodeKey string `json:"nodeKey"` + TypeKey string `json:"typeKey"` + ID string `json:"id"` + Expired bool `json:"expired"` +} +type mission struct { + Node string `json:"node"` + Faction string `json:"faction"` + Type string `json:"type"` +} +type sentientOutposts struct { + Mission mission `json:"mission"` + Activation time.Time `json:"activation"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + ID string `json:"id"` +} +type currentReward struct { + Name string `json:"name"` + Cost int `json:"cost"` +} +type rotation struct { + Name string `json:"name"` + Cost int `json:"cost"` +} +type evergreens struct { + Name string `json:"name"` + Cost int `json:"cost"` +} +type incursions struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + Expiry time.Time `json:"expiry"` +} +type steelPath struct { + CurrentReward currentReward `json:"currentReward"` + Activation time.Time `json:"activation"` + Expiry time.Time `json:"expiry"` + Remaining string `json:"remaining"` + Rotation []rotation `json:"rotation"` + Evergreens []evergreens `json:"evergreens"` + Incursions incursions `json:"incursions"` +} +type inventory struct { + Item string `json:"item"` + Ducats int `json:"ducats"` + Credits interface{} `json:"credits"` +} +type schedule struct { + Expiry time.Time `json:"expiry"` + Item string `json:"item"` +} +type vaultTrader struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + Character string `json:"character"` + Location string `json:"location"` + Inventory []inventory `json:"inventory"` + PsID string `json:"psId"` + EndString string `json:"endString"` + InitialStart time.Time `json:"initialStart"` + Completed bool `json:"completed"` + Schedule []schedule `json:"schedule"` +} +type alerts struct { + ID string `json:"id"` + Activation time.Time `json:"activation"` + StartString string `json:"startString"` + Expiry time.Time `json:"expiry"` + Active bool `json:"active"` + Mission struct { + Description string `json:"description"` + Node string `json:"node"` + NodeKey string `json:"nodeKey"` + Type string `json:"type"` + TypeKey string `json:"typeKey"` + Faction string `json:"faction"` + Reward struct { + Items []interface{} `json:"items"` + CountedItems []struct { + Count int `json:"count"` + Type string `json:"type"` + Key string `json:"key"` + } `json:"countedItems"` + Credits int `json:"credits"` + AsString string `json:"asString"` + ItemString string `json:"itemString"` + Thumbnail string `json:"thumbnail"` + Color int `json:"color"` + } `json:"reward"` + MinEnemyLevel int `json:"minEnemyLevel"` + MaxEnemyLevel int `json:"maxEnemyLevel"` + MaxWaveNum int `json:"maxWaveNum"` + Nightmare bool `json:"nightmare"` + ArchwingRequired bool `json:"archwingRequired"` + IsSharkwing bool `json:"isSharkwing"` + LevelOverride string `json:"levelOverride"` + EnemySpec string `json:"enemySpec"` + AdvancedSpawners []interface{} `json:"advancedSpawners"` + RequiredItems []interface{} `json:"requiredItems"` + LevelAuras []interface{} `json:"levelAuras"` + } `json:"mission"` + Eta string `json:"eta"` + RewardTypes []string `json:"rewardTypes"` + Tag string `json:"tag"` +} +type wfAPIItem struct { + Payload payload `json:"payload"` +} +type items struct { + URLName string `json:"url_name"` + Thumb string `json:"thumb"` + ItemName string `json:"item_name"` + ID string `json:"id"` + Vaulted bool `json:"vaulted,omitempty"` +} +type payload struct { + Items []items `json:"items"` + Orders orders `json:"orders"` +} + +type wfAPIItemsOrders struct { + Payload payload `json:"payload"` + Include include `json:"include"` +} +type user struct { + IngameName string `json:"ingame_name"` + LastSeen time.Time `json:"last_seen"` + Reputation int `json:"reputation"` + Region string `json:"region"` + ID string `json:"id"` + Avatar interface{} `json:"avatar"` + Status string `json:"status"` +} +type orders []struct { + OrderType string `json:"order_type"` + LastUpdate time.Time `json:"last_update"` + Region string `json:"region"` + Quantity int `json:"quantity"` + Visible bool `json:"visible"` + CreationDate time.Time `json:"creation_date"` + Platinum int `json:"platinum"` + Platform string `json:"platform"` + User user `json:"user"` + ID string `json:"id"` + ModRank int `json:"mod_rank"` +} + +func (a orders) Len() int { // 重写 Len() 方法 + return len(a) +} +func (a orders) Swap(i, j int) { // 重写 Swap() 方法 + a[i], a[j] = a[j], a[i] +} +func (a orders) Less(i, j int) bool { // 重写 Less() 方法, 从大到小排序 + return a[i].Platinum < a[j].Platinum +} + +type en struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type ru struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type ko struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type fr struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type sv struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type de struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type zhHant struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type zhHans struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type pt struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type es struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type pl struct { + ItemName string `json:"item_name"` + Description string `json:"description"` + WikiLink string `json:"wiki_link"` + Drop []interface{} `json:"drop"` +} +type itemsInSet struct { + Icon string `json:"icon"` + URLName string `json:"url_name"` + SubIcon string `json:"sub_icon"` + ModMaxRank int `json:"mod_max_rank"` + Thumb string `json:"thumb"` + SetRoot bool `json:"set_root"` + QuantityForSet int `json:"quantity_for_set,omitempty"` + ID string `json:"id"` + TradingTax int `json:"trading_tax"` + Tags []string `json:"tags"` + MasteryLevel int `json:"mastery_level"` + Ducats int `json:"ducats"` + IconFormat string `json:"icon_format"` + En en `json:"en"` + Ru ru `json:"ru"` + Ko ko `json:"ko"` + Fr fr `json:"fr"` + Sv sv `json:"sv"` + De de `json:"de"` + ZhHant zhHant `json:"zh-hant"` + ZhHans zhHans `json:"zh-hans"` + Pt pt `json:"pt"` + Es es `json:"es"` + Pl pl `json:"pl"` +} +type item struct { + ID string `json:"id"` + ItemsInSet []itemsInSet `json:"items_in_set"` +} +type include struct { + Item item `json:"item"` +}