ZeroBot-Plugin/plugin/warframeapi/main.go
github-actions[bot] f18c809355
🎨 改进代码样式 (#571)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 16:30:11 +08:00

503 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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/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
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)
}
}