mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-19 13:59:39 +08:00
全新的guessmusic插件 (#499)
This commit is contained in:
parent
1c93611a16
commit
743e5509a2
39
README.md
39
README.md
@ -734,37 +734,32 @@ print("run[CQ:image,file="+j["img"]+"]")
|
|||||||
|
|
||||||
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/guessmusic"`
|
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/guessmusic"`
|
||||||
|
|
||||||
- 猜歌插件(该插件依赖ffmpeg)
|
猜歌插件(该插件依赖ffmpeg)
|
||||||
|
|
||||||
- 因为API不可抗因素,更改为了本地猜歌,但仍支持歌曲下载(VIP歌曲无法下载,黑胶可以)
|
|
||||||
|
|
||||||
|
---------主 人 指 令---------
|
||||||
- [x] 设置猜歌歌库路径 [绝对路径]
|
- [x] 设置猜歌歌库路径 [绝对路径]
|
||||||
|
- [x] [创建/删除]歌单 [歌单名称]
|
||||||
|
- [x] 下载歌曲[歌曲名称/网易云歌曲ID]到[歌单名称]
|
||||||
|
|
||||||
- [x] 猜歌[开启/关闭][歌单/歌词]自动下载
|
-------管 理 员 指 令--------
|
||||||
|
|
||||||
- 现只有歌词指令有效
|
|
||||||
|
|
||||||
- [ ] 添加歌单 [网易云歌单链接/ID] [歌单名称]
|
|
||||||
|
|
||||||
- [x] 下载歌曲 [歌曲名称/网易云歌曲ID] [歌单名称]
|
|
||||||
|
|
||||||
- [x] 删除歌单 [网易云歌单ID/歌单名称]
|
|
||||||
|
|
||||||
- 注:删除网易云歌单ID仅只是解除绑定,删除歌单名称是将本地数据全部删除!
|
|
||||||
|
|
||||||
- [x] 设置猜歌默认歌单 [歌单名称]
|
- [x] 设置猜歌默认歌单 [歌单名称]
|
||||||
|
- [x] 上传歌曲[群文件的音乐名]到[歌单名称]
|
||||||
|
|
||||||
|
------公 用 指 令------
|
||||||
- [x] 歌单列表
|
- [x] 歌单列表
|
||||||
|
|
||||||
- [x] [个人/团队]猜歌
|
- [x] [个人/团队]猜歌
|
||||||
|
|
||||||
- 注:默认歌库为歌单列表第一个,如果设置了默认歌单变为指定的歌单
|
------插 件 扩 展------
|
||||||
|
|
||||||
- 可在“[个人/团队]猜歌指令”后面添加[-歌单名称]进行指定歌单猜歌
|
NeteaseCloudMusicApi项目地址:https://binaryify.github.io/NeteaseCloudMusicApi/#/
|
||||||
|
- [x] 设置猜歌API帮助
|
||||||
- 猜歌内容必须以[-]开头才会识别
|
- [x] 设置猜歌API [API首页网址]
|
||||||
|
- [x] 猜歌[开启/关闭][歌单/歌词]自动下载
|
||||||
- 本地歌曲命名规则为:\n歌名 - 歌手 - 其他(歌曲出处之类)
|
- [ ] 登录网易云
|
||||||
|
- [x] 歌单信息 [网易云歌单链接/ID]
|
||||||
|
- [x] [歌单名称]绑定网易云[网易云歌单链接/ID]
|
||||||
|
- [x] 下载歌单[网易云歌单链接/ID]到[歌单名称]
|
||||||
|
- [x] 解除绑定 [歌单名称]
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
484
plugin/guessmusic/apiservice.go
Normal file
484
plugin/guessmusic/apiservice.go
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
package guessmusic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
wyy "github.com/FloatTech/AnimeAPI/neteasemusic"
|
||||||
|
"github.com/FloatTech/floatbox/file"
|
||||||
|
"github.com/FloatTech/floatbox/web"
|
||||||
|
"github.com/FloatTech/zbputils/ctxext"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
zero "github.com/wdvxdr1123/ZeroBot"
|
||||||
|
"github.com/wdvxdr1123/ZeroBot/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// API配置
|
||||||
|
engine.OnPrefix("设置猜歌API", zero.SuperUserPermission).SetBlock(true).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
option := ctx.State["args"].(string)
|
||||||
|
if option == "帮助" {
|
||||||
|
ctx.SendChain(message.Text(
|
||||||
|
"项目地址:binaryify.github.io/NeteaseCloudMusicApi" +
|
||||||
|
"\n网上有基于该框架的API,可以自行搜索白嫖。\n" +
|
||||||
|
"添加API指令:\n设置猜歌API [API首页网址]"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(option, "/") {
|
||||||
|
option += "/"
|
||||||
|
}
|
||||||
|
cfg.APIURL = option
|
||||||
|
err := saveConfig(cfgFile)
|
||||||
|
if err == nil {
|
||||||
|
ctx.SendChain(message.Text("成功!"))
|
||||||
|
} else {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// API配置
|
||||||
|
engine.OnRegex(`^猜歌(开启|关闭)(歌单|歌词)自动下载`, zero.SuperUserPermission).SetBlock(true).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
swtich := ctx.State["regex_matched"].([]string)[1]
|
||||||
|
option := ctx.State["regex_matched"].([]string)[2]
|
||||||
|
chose := true
|
||||||
|
if swtich == "关闭" {
|
||||||
|
chose = false
|
||||||
|
}
|
||||||
|
if option == "歌单" {
|
||||||
|
cfg.API = chose
|
||||||
|
} else {
|
||||||
|
cfg.Local = chose
|
||||||
|
}
|
||||||
|
err := saveConfig(cfgFile)
|
||||||
|
if err == nil {
|
||||||
|
ctx.SendChain(message.Text("成功!"))
|
||||||
|
} else {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
engine.OnFullMatch("登录网易云", zero.SuperUserPermission, func(ctx *zero.Ctx) bool {
|
||||||
|
if !zero.OnlyPrivate(ctx) {
|
||||||
|
ctx.SendChain(message.Text("为了保护登录过程,请bot主人私聊。"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}).SetBlock(true).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
keyURL := cfg.APIURL + "login/qr/key"
|
||||||
|
data, err := web.GetData(keyURL)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, "获取网易云key失败,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var keyInfo keyInfo
|
||||||
|
err = json.Unmarshal(data, &keyInfo)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, "解析网易云key失败,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
qrURL := cfg.APIURL + "login/qr/create?key=" + keyInfo.Data.Unikey + "&qrimg=1"
|
||||||
|
data, err = web.GetData(qrURL)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, "获取网易云二维码失败,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var qrInfo qrInfo
|
||||||
|
err = json.Unmarshal(data, &qrInfo)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, "解析网易云二维码失败,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.SendChain(message.Text("[请使用手机APP扫描二维码或者进入网页扫码登录]\n", qrInfo.Data.Qrurl),
|
||||||
|
message.Image("base64://"+strings.ReplaceAll(qrInfo.Data.Qrimg, "data:image/png;base64,", "")),
|
||||||
|
message.Text("二维码有效时间为6分钟,登陆后请耐心等待结果,获取cookie过程有些漫长。"))
|
||||||
|
i := 0
|
||||||
|
for range time.NewTicker(10 * time.Second).C {
|
||||||
|
APIURL := cfg.APIURL + "login/qr/check?key=" + url.QueryEscape(keyInfo.Data.Unikey)
|
||||||
|
data, err := web.GetData(APIURL)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, "无法获取登录状态,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var cookiesInfo cookyInfo
|
||||||
|
err = json.Unmarshal(data, &cookiesInfo)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, "解析登录状态失败,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch cookiesInfo.Code {
|
||||||
|
case 803:
|
||||||
|
cfg.Cookie = cookiesInfo.Cookie
|
||||||
|
err = saveConfig(cfgFile)
|
||||||
|
if err == nil {
|
||||||
|
ctx.SendChain(message.Text("成功!"))
|
||||||
|
} else {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case 801:
|
||||||
|
i++
|
||||||
|
if i%6 == 0 { // 每1分钟才提醒一次,减少提示(380/60=6次)
|
||||||
|
ctx.SendChain(message.Text("状态:", cookiesInfo.Message))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case 800:
|
||||||
|
ctx.SendChain(message.Text("状态:", cookiesInfo.Message))
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
ctx.SendChain(message.Text("状态:", cookiesInfo.Message))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
engine.OnRegex(`^歌单信息\s*((https:\/\/music\.163\.com\/#\/playlist\?id=)?(\d+)|http:\/\/music\.163\.com\/playlist\/(\d+).*)$`).SetBlock(true).Limit(ctxext.LimitByGroup).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
listID := ctx.State["regex_matched"].([]string)[3] + ctx.State["regex_matched"].([]string)[4]
|
||||||
|
_, err := strconv.ParseInt(listID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("请输入正确的歌单ID或者歌单连接"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
APIURL := cfg.APIURL + "playlist/detail?id=" + listID
|
||||||
|
data, err := web.GetData(APIURL)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("无法连接歌单,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var parsed listInfoOfAPI
|
||||||
|
err = json.Unmarshal(data, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("无法解析歌单ID内容,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.SendChain(
|
||||||
|
message.Image(parsed.Playlist.CoverImgURL),
|
||||||
|
message.Text(
|
||||||
|
"歌单名称:", parsed.Playlist.Name,
|
||||||
|
"\n歌单ID:", parsed.Playlist.ID,
|
||||||
|
"\n创建人:", parsed.Playlist.Creator.Nickname,
|
||||||
|
"\n创建时间:", time.Unix(parsed.Playlist.CreateTime/1000, 0).Format("2006-01-02"),
|
||||||
|
"\n标签:", strings.Join(parsed.Playlist.Tags, ";"),
|
||||||
|
"\n歌曲数量:", parsed.Playlist.TrackCount,
|
||||||
|
"\n歌单简介:\n", parsed.Playlist.Description,
|
||||||
|
"\n更新时间:", time.Unix(parsed.Playlist.UpdateTime/1000, 0).Format("2006-01-02"),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
// 本地绑定网易云歌单ID
|
||||||
|
engine.OnRegex(`^(.*)绑定网易云\s*((https:\/\/music\.163\.com\/#\/playlist\?id=)?(\d+)|http:\/\/music\.163\.com\/playlist\/(\d+).*)$`, zero.SuperUserPermission).SetBlock(true).Limit(ctxext.LimitByGroup).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
listName := ctx.State["regex_matched"].([]string)[1]
|
||||||
|
listID := ctx.State["regex_matched"].([]string)[4] + ctx.State["regex_matched"].([]string)[5]
|
||||||
|
ctx.SendChain(message.Text("正在校验歌单信息,请稍等"))
|
||||||
|
pathOfMusic := cfg.MusicPath + listName + "/"
|
||||||
|
if file.IsNotExist(pathOfMusic) {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, "歌单不存在于本地"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 是否存在该歌单
|
||||||
|
APIURL := cfg.APIURL + "playlist/track/all?id=" + listID
|
||||||
|
data, err := web.GetData(APIURL)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var parsed musicListOfApI
|
||||||
|
err = json.Unmarshal(data, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, "无法解析歌单ID内容,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if parsed.Code != 200 {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, parsed.Code))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mid, _ := strconv.ParseInt(listID, 10, 64)
|
||||||
|
cfg.Playlist = append(cfg.Playlist, listRaw{
|
||||||
|
Name: listName,
|
||||||
|
ID: mid,
|
||||||
|
})
|
||||||
|
err = saveConfig(cfgFile)
|
||||||
|
if err == nil {
|
||||||
|
ctx.SendChain(message.Text("成功!"))
|
||||||
|
} else {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
engine.OnPrefix("解除绑定", zero.SuperUserPermission).SetBlock(true).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
delList := ctx.State["args"].(string)
|
||||||
|
filelist, err := getlist(cfg.MusicPath)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var playID int64
|
||||||
|
for _, listinfo := range filelist {
|
||||||
|
if delList == listinfo.Name {
|
||||||
|
playID = listinfo.ID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 删除ID
|
||||||
|
if playID == 0 { // 如果ID没有且没删除文件
|
||||||
|
ctx.SendChain(message.Text("歌单名称错误或者该歌单并没有绑定网易云,可以发送“歌单列表”获取歌单名称"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
index := -1
|
||||||
|
for i, list := range cfg.Playlist {
|
||||||
|
if playID == list.ID {
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if index == -1 {
|
||||||
|
ctx.SendChain(message.Text("歌单名称错误或者该歌单并没有绑定网易云,可以发送“歌单列表”获取歌单名称"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.Playlist = append(cfg.Playlist[:index], cfg.Playlist[index+1:]...)
|
||||||
|
err = saveConfig(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
ctx.SendChain(message.Text("成功!"))
|
||||||
|
} else {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 下载歌曲到对应的歌单里面
|
||||||
|
engine.OnRegex(`^下载歌单\s*((https:\/\/music\.163\.com\/#\/playlist\?id=)?(\d+)|http:\/\/music\.163\.com\/playlist\/(\d+).*[^\s$])\s*到\s*(.*)$`, zero.SuperUserPermission).SetBlock(true).Limit(ctxext.LimitByGroup).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
keyword := ctx.State["regex_matched"].([]string)[3] + ctx.State["regex_matched"].([]string)[4]
|
||||||
|
listName := ctx.State["regex_matched"].([]string)[5]
|
||||||
|
ctx.SendChain(message.Text("正在校验歌单信息,请稍等"))
|
||||||
|
// 是否存在该歌单
|
||||||
|
filelist, err := getlist(cfg.MusicPath)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, "获取歌单列表ERROR:", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok := true
|
||||||
|
for _, listinfo := range filelist {
|
||||||
|
if listName == listinfo.Name {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
ctx.SendChain(message.Text("歌单不存在,是否创建?(是/否)"))
|
||||||
|
next := zero.NewFutureEvent("message", 999, false, zero.OnlyGroup, zero.RegexRule(`(是|否)`), ctx.CheckSession())
|
||||||
|
recv, cancel := next.Repeat()
|
||||||
|
defer cancel()
|
||||||
|
wait := time.NewTimer(120 * time.Second)
|
||||||
|
answer := ""
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-wait.C:
|
||||||
|
wait.Stop()
|
||||||
|
ctx.SendChain(message.Text("等待超时,取消下载"))
|
||||||
|
return
|
||||||
|
case c := <-recv:
|
||||||
|
wait.Stop()
|
||||||
|
answer = c.Event.Message.String()
|
||||||
|
}
|
||||||
|
if answer == "否" {
|
||||||
|
ctx.SendChain(message.Text("下载已经取消"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if answer != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(cfg.MusicPath+listName, 0755)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listID, err := strconv.ParseInt(keyword, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
err = downloadlist(listID, cfg.MusicPath+listName+"/")
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
ctx.SendChain(message.Text("成功!"))
|
||||||
|
} else {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 随机从歌单下载歌曲(歌单ID, 音乐保存路径)
|
||||||
|
func drawByAPI(playlistID int64, musicPath string) (musicName string, err error) {
|
||||||
|
APIURL := cfg.APIURL + "playlist/track/all?id=" + strconv.FormatInt(playlistID, 10)
|
||||||
|
data, err := web.GetData(APIURL)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Errorf("无法获取歌单列表\n%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var parsed musicListOfApI
|
||||||
|
err = json.Unmarshal(data, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Errorf("无法读取歌单列表\n%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listlen := len(parsed.Songs)
|
||||||
|
randidx := rand.Intn(listlen)
|
||||||
|
// 将"/"符号去除,不然无法生成文件
|
||||||
|
name := strings.ReplaceAll(parsed.Songs[randidx].Name, "/", "·")
|
||||||
|
musicID := parsed.Songs[randidx].ID
|
||||||
|
artistName := ""
|
||||||
|
for i, ARInfo := range parsed.Songs[randidx].Ar {
|
||||||
|
if i != 0 {
|
||||||
|
artistName += "&" + ARInfo.Name
|
||||||
|
} else {
|
||||||
|
artistName += ARInfo.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cource := ""
|
||||||
|
if parsed.Songs[randidx].Alia != nil {
|
||||||
|
cource = strings.Join(parsed.Songs[randidx].Alia, "&")
|
||||||
|
// 将"/"符号去除,不然无法下载
|
||||||
|
cource = strings.ReplaceAll(cource, "/", "&")
|
||||||
|
}
|
||||||
|
if name == "" || musicID == 0 {
|
||||||
|
err = errors.New("无法获API取歌曲信息")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cource != "" {
|
||||||
|
name += " - " + artistName + " - " + cource
|
||||||
|
} else {
|
||||||
|
name += " - " + artistName
|
||||||
|
}
|
||||||
|
// 下载歌曲
|
||||||
|
err = wyy.DownloadMusic(musicID, name, musicPath)
|
||||||
|
if err == nil {
|
||||||
|
musicName = name + ".mp3"
|
||||||
|
if cfg.Local {
|
||||||
|
// 下载歌词
|
||||||
|
_ = wyy.DownloadLrc(musicID, name, musicPath+"歌词/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载歌单歌曲(歌单ID, 音乐保存路径)
|
||||||
|
func downloadlist(playlistID int64, musicPath string) error {
|
||||||
|
APIURL := cfg.APIURL + "playlist/track/all?id=" + strconv.FormatInt(playlistID, 10)
|
||||||
|
data, err := web.GetData(APIURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var parsed musicListOfApI
|
||||||
|
err = json.Unmarshal(data, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if parsed.Code != 200 {
|
||||||
|
err = errors.Errorf("requset code : %d", parsed.Code)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, info := range parsed.Songs {
|
||||||
|
// 将"/"符号去除,不然无法生成文件
|
||||||
|
musicName := strings.ReplaceAll(info.Name, "/", "·")
|
||||||
|
musicID := info.ID
|
||||||
|
artistName := ""
|
||||||
|
for i, ARInfo := range info.Ar {
|
||||||
|
if i != 0 {
|
||||||
|
artistName += "&" + ARInfo.Name
|
||||||
|
} else {
|
||||||
|
artistName += ARInfo.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cource := ""
|
||||||
|
if info.Alia != nil {
|
||||||
|
cource = strings.Join(info.Alia, "&")
|
||||||
|
// 将"/"符号去除,不然无法下载
|
||||||
|
cource = strings.ReplaceAll(cource, "/", "&")
|
||||||
|
}
|
||||||
|
if musicName == "" || musicID == 0 {
|
||||||
|
err = errors.New("无法获API取歌曲信息")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cource != "" {
|
||||||
|
musicName += " - " + artistName + " - " + cource
|
||||||
|
} else {
|
||||||
|
musicName += " - " + artistName
|
||||||
|
}
|
||||||
|
// 下载歌曲
|
||||||
|
err = wyy.DownloadMusic(musicID, musicName, musicPath)
|
||||||
|
if err == nil {
|
||||||
|
if cfg.Local {
|
||||||
|
// 下载歌词
|
||||||
|
_ = wyy.DownloadLrc(musicID, musicName, musicPath+"歌词/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************/
|
||||||
|
/**************************独角兽API*******************************/
|
||||||
|
/*****************************************************************/
|
||||||
|
// 下载从独角兽抽到的歌曲ID(歌单ID, 音乐保存路径, 歌词保存路径)
|
||||||
|
func downloadByOvooa(playlistID int64, musicPath string) (musicName string, err error) {
|
||||||
|
// 抽取歌曲
|
||||||
|
mid, err := drawByOvooa(playlistID)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Errorf("API%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取完成的歌名
|
||||||
|
musiclist, err := wyy.SearchMusic(strconv.Itoa(mid), 1)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Errorf("API歌曲下载ERROR: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 歌曲ID理论是唯一的
|
||||||
|
mun := len(musiclist)
|
||||||
|
if mun == 1 {
|
||||||
|
// 拉取歌名
|
||||||
|
musicList := make([]string, mun)
|
||||||
|
i := 0
|
||||||
|
for musicName := range musiclist {
|
||||||
|
musicList[i] = musicName
|
||||||
|
}
|
||||||
|
name := musicList[0]
|
||||||
|
// 下载歌曲
|
||||||
|
err = wyy.DownloadMusic(mid, name, musicPath)
|
||||||
|
if err == nil {
|
||||||
|
musicName = name + ".mp3"
|
||||||
|
if cfg.Local {
|
||||||
|
// 下载歌词
|
||||||
|
_ = wyy.DownloadLrc(mid, name, musicPath+"歌词/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errors.Errorf("music IDThis music ID sreached munber is %d", mun)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过独角兽API随机抽取歌单歌曲ID(参数:歌单ID)
|
||||||
|
func drawByOvooa(playlistID int64) (musicID int, err error) {
|
||||||
|
APIURL := "https://ovooa.com/API/163_Music_Rand/api.php?id=" + strconv.FormatInt(playlistID, 10)
|
||||||
|
data, err := web.GetData(APIURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var parsed ovooaData
|
||||||
|
err = json.Unmarshal(data, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if parsed.Code != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return parsed.Data.ID, nil
|
||||||
|
}
|
||||||
336
plugin/guessmusic/guessmusic.go
Normal file
336
plugin/guessmusic/guessmusic.go
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
package guessmusic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/fs"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/FloatTech/floatbox/file"
|
||||||
|
"github.com/FloatTech/zbputils/ctxext"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
zero "github.com/wdvxdr1123/ZeroBot"
|
||||||
|
"github.com/wdvxdr1123/ZeroBot/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cuttime = [...]string{"00:00:05", "00:00:30", "00:01:00"} // 音乐切割时间点,可自行调节时间(时:分:秒)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
engine.OnRegex(`^(个人|团队)猜歌(-(.*))?$`, zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByGroup).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
mode := ctx.State["regex_matched"].([]string)[3]
|
||||||
|
gid := ctx.Event.GroupID
|
||||||
|
// 获取本地列表
|
||||||
|
filelist, err := getlist(cfg.MusicPath)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 加载默认歌单
|
||||||
|
if mode == "" {
|
||||||
|
index := -1
|
||||||
|
for i, dlist := range cfg.Defaultlist {
|
||||||
|
if dlist.GroupID == gid {
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if index == -1 {
|
||||||
|
// 如果没有设置就默认第一个文件夹
|
||||||
|
mode = filelist[0].Name
|
||||||
|
} else {
|
||||||
|
mode = cfg.Defaultlist[index].Name
|
||||||
|
ok := true
|
||||||
|
for _, listinfo := range filelist {
|
||||||
|
if mode == listinfo.Name {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果默认的歌单不存在了清空设置
|
||||||
|
if ok {
|
||||||
|
cfg.Defaultlist = append(cfg.Defaultlist[:index], cfg.Defaultlist[index+1:]...)
|
||||||
|
_ = saveConfig(cfgFile)
|
||||||
|
mode = filelist[0].Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.SendChain(message.Text("正在准备歌曲,请稍等\n回答“-[歌曲信息(歌名歌手等)|提示|取消]”\n一共3段语音,6次机会"))
|
||||||
|
// 随机抽歌
|
||||||
|
pathOfMusic, musicName, err := musicLottery(cfg.MusicPath, mode)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(serviceErr, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 解析歌曲信息
|
||||||
|
music := strings.Split(musicName, ".")
|
||||||
|
// 获取音乐后缀
|
||||||
|
musictype := music[len(music)-1]
|
||||||
|
if !strings.Contains(musictypelist, musictype) {
|
||||||
|
ctx.SendChain(message.Text("抽取到了歌曲:\n",
|
||||||
|
musicName, "\n该歌曲不是音乐后缀,请联系bot主人修改"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取音乐信息
|
||||||
|
musicInfo := strings.Split(strings.ReplaceAll(musicName, "."+musictype, ""), " - ")
|
||||||
|
infoNum := len(musicInfo)
|
||||||
|
if infoNum == 1 {
|
||||||
|
ctx.SendChain(message.Text("抽取到了歌曲:\n",
|
||||||
|
musicName, "\n该歌曲命名不符合命名规则,请联系bot主人修改"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
answerString := "歌名:" + musicInfo[0] + "\n歌手:" + musicInfo[1]
|
||||||
|
musicAlia := ""
|
||||||
|
if infoNum > 2 {
|
||||||
|
musicAlia = musicInfo[2]
|
||||||
|
answerString += "\n其他信息:\n" + strings.ReplaceAll(musicAlia, "&", "\n")
|
||||||
|
}
|
||||||
|
// 切割音频,生成3个10秒的音频
|
||||||
|
outputPath := cachePath + strconv.FormatInt(gid, 10) + "/"
|
||||||
|
err = cutMusic(musicName, pathOfMusic, outputPath)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 进行猜歌环节
|
||||||
|
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + "0.wav"))
|
||||||
|
var next *zero.FutureEvent
|
||||||
|
if ctx.State["regex_matched"].([]string)[1] == "个人" {
|
||||||
|
next = zero.NewFutureEvent("message", 999, false, zero.OnlyGroup, zero.RegexRule(`^-\S{1,}`), ctx.CheckSession())
|
||||||
|
} else {
|
||||||
|
next = zero.NewFutureEvent("message", 999, false, zero.OnlyGroup, zero.RegexRule(`^-\S{1,}`), zero.CheckGroup(ctx.Event.GroupID))
|
||||||
|
}
|
||||||
|
var musicCount = 0 // 音频数量
|
||||||
|
var answerCount = 0 // 问答次数
|
||||||
|
recv, cancel := next.Repeat()
|
||||||
|
defer cancel()
|
||||||
|
wait := time.NewTimer(40 * time.Second)
|
||||||
|
tick := time.NewTimer(105 * time.Second)
|
||||||
|
after := time.NewTimer(120 * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick.C:
|
||||||
|
ctx.SendChain(message.Text("猜歌游戏,你还有15s作答时间"))
|
||||||
|
case <-after.C:
|
||||||
|
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID,
|
||||||
|
message.Text("时间超时,猜歌结束,公布答案:\n", answerString)))
|
||||||
|
return
|
||||||
|
case <-wait.C:
|
||||||
|
wait.Reset(40 * time.Second)
|
||||||
|
musicCount++
|
||||||
|
if musicCount > 2 {
|
||||||
|
wait.Stop()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx.SendChain(
|
||||||
|
message.Text("好像有些难度呢,再听这段音频,要仔细听哦"),
|
||||||
|
)
|
||||||
|
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + strconv.Itoa(musicCount) + ".wav"))
|
||||||
|
case c := <-recv:
|
||||||
|
wait.Reset(40 * time.Second)
|
||||||
|
tick.Reset(105 * time.Second)
|
||||||
|
after.Reset(120 * time.Second)
|
||||||
|
answer := strings.Replace(c.Event.Message.String(), "-", "", 1)
|
||||||
|
switch {
|
||||||
|
case answer == "取消":
|
||||||
|
if c.Event.UserID == ctx.Event.UserID {
|
||||||
|
wait.Stop()
|
||||||
|
tick.Stop()
|
||||||
|
after.Stop()
|
||||||
|
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID,
|
||||||
|
message.Text("游戏已取消,猜歌答案是\n", answerString, "\n\n\n下面欣赏猜歌的歌曲")))
|
||||||
|
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("你无权限取消"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case answer == "提示":
|
||||||
|
musicCount++
|
||||||
|
if musicCount > 2 {
|
||||||
|
wait.Stop()
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("已经没有提示了哦"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wait.Reset(40 * time.Second)
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("再听这段音频,要仔细听哦"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + strconv.Itoa(musicCount) + ".wav"))
|
||||||
|
case strings.Contains(musicInfo[0], answer) || strings.EqualFold(musicInfo[0], answer):
|
||||||
|
wait.Stop()
|
||||||
|
tick.Stop()
|
||||||
|
after.Stop()
|
||||||
|
ctx.Send(message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("太棒了,你猜对歌曲名了!答案是\n", answerString, "\n\n下面欣赏猜歌的歌曲")))
|
||||||
|
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
|
||||||
|
return
|
||||||
|
case strings.Contains(musicInfo[1], answer) || strings.EqualFold(musicInfo[1], answer):
|
||||||
|
wait.Stop()
|
||||||
|
tick.Stop()
|
||||||
|
after.Stop()
|
||||||
|
ctx.Send(message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("太棒了,你猜对歌手名了!答案是\n", answerString, "\n\n下面欣赏猜歌的歌曲")))
|
||||||
|
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
|
||||||
|
return
|
||||||
|
case strings.Contains(musicAlia, answer) || strings.EqualFold(musicAlia, answer):
|
||||||
|
wait.Stop()
|
||||||
|
tick.Stop()
|
||||||
|
after.Stop()
|
||||||
|
ctx.Send(message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("太棒了,你猜对出处了!答案是\n", answerString, "\n\n下面欣赏猜歌的歌曲")))
|
||||||
|
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
musicCount++
|
||||||
|
switch {
|
||||||
|
case musicCount > 2 && answerCount < 6:
|
||||||
|
wait.Stop()
|
||||||
|
answerCount++
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("答案不对哦,加油啊~"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case musicCount > 2:
|
||||||
|
wait.Stop()
|
||||||
|
tick.Stop()
|
||||||
|
after.Stop()
|
||||||
|
ctx.Send(message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("次数到了,没能猜出来。答案是\n", answerString, "\n\n下面欣赏猜歌的歌曲")))
|
||||||
|
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
wait.Reset(40 * time.Second)
|
||||||
|
answerCount++
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("答案不对,再听这段音频,要仔细听哦"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + strconv.Itoa(musicCount) + ".wav"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 随机抽取音乐
|
||||||
|
func musicLottery(musicPath, listName string) (pathOfMusic, musicName string, err error) {
|
||||||
|
// 读取歌单文件
|
||||||
|
pathOfMusic = musicPath + listName + "/"
|
||||||
|
if file.IsNotExist(pathOfMusic) {
|
||||||
|
err = errors.New("指定的歌单不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
files, err := os.ReadDir(pathOfMusic)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取绑定的网易云
|
||||||
|
var playlistID int64
|
||||||
|
for _, listinfo := range cfg.Playlist {
|
||||||
|
if listinfo.Name == listName {
|
||||||
|
playlistID = listinfo.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果本地列表为空
|
||||||
|
if len(files) == 0 {
|
||||||
|
if playlistID == 0 || !cfg.API {
|
||||||
|
err = errors.New("本地歌单数据为0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 如果绑定了歌单ID
|
||||||
|
if cfg.APIURL == "" {
|
||||||
|
// 如果没有配置过API地址,尝试连接独角兽
|
||||||
|
musicName, err = downloadByOvooa(playlistID, pathOfMusic)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Errorf("本地歌单数据为0,API下载歌曲失败\n%s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 从API中抽取歌曲
|
||||||
|
musicName, err = drawByAPI(playlistID, pathOfMusic)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Errorf("本地歌单数据为0,API下载歌曲失败\n%s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 进行随机抽取
|
||||||
|
if playlistID == 0 || !cfg.API {
|
||||||
|
musicName = getLocalMusic(files)
|
||||||
|
} else {
|
||||||
|
switch rand.Intn(3) { // 三分二概率抽取API的
|
||||||
|
case 1:
|
||||||
|
musicName = getLocalMusic(files)
|
||||||
|
default:
|
||||||
|
if cfg.APIURL == "" {
|
||||||
|
// 如果没有配置过API地址,尝试连接独角兽
|
||||||
|
musicName, err = downloadByOvooa(playlistID, pathOfMusic)
|
||||||
|
} else {
|
||||||
|
// 从API中抽取歌曲
|
||||||
|
musicName, err = drawByAPI(playlistID, pathOfMusic)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
musicName = getLocalMusic(files)
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从本地列表中随机抽取一首
|
||||||
|
func getLocalMusic(files []fs.DirEntry) (musicName string) {
|
||||||
|
if len(files) > 1 {
|
||||||
|
music := files[rand.Intn(len(files))]
|
||||||
|
// 如果是文件夹就递归
|
||||||
|
if music.IsDir() {
|
||||||
|
musicName = getLocalMusic(files)
|
||||||
|
} else {
|
||||||
|
musicName = music.Name()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
music := files[0]
|
||||||
|
if !music.IsDir() {
|
||||||
|
musicName = files[0].Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切割音乐成三个10s音频
|
||||||
|
func cutMusic(musicName, pathOfMusic, outputPath string) (err error) {
|
||||||
|
err = os.MkdirAll(outputPath, 0755)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Errorf("[生成歌曲目录错误]ERROR: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmdArguments := []string{"-y", "-i", pathOfMusic + musicName,
|
||||||
|
"-ss", cuttime[0], "-t", "10", file.BOTPATH + "/" + outputPath + "0.wav",
|
||||||
|
"-ss", cuttime[1], "-t", "10", file.BOTPATH + "/" + outputPath + "1.wav",
|
||||||
|
"-ss", cuttime[2], "-t", "10", file.BOTPATH + "/" + outputPath + "2.wav", "-hide_banner"}
|
||||||
|
cmd := exec.Command("ffmpeg", cmdArguments...)
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Errorf("[生成歌曲错误]ERROR: %s", stderr.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -3,11 +3,12 @@ package guessmusic
|
|||||||
// config内容
|
// config内容
|
||||||
type config struct {
|
type config struct {
|
||||||
MusicPath string `json:"musicPath"`
|
MusicPath string `json:"musicPath"`
|
||||||
|
APIURL string `json:"apiURL"`
|
||||||
|
Playlist []listRaw `json:"playlist"`
|
||||||
|
Defaultlist []dlist `json:"defaultlist"`
|
||||||
Local bool `json:"local"`
|
Local bool `json:"local"`
|
||||||
API bool `json:"api"`
|
API bool `json:"api"`
|
||||||
Cookie string `json:"cookie"`
|
Cookie string `json:"cookie"`
|
||||||
Playlist []listRaw `json:"playlist"`
|
|
||||||
Defaultlist []dlist `json:"defaultlist"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录歌单绑定的网易云歌单ID
|
// 记录歌单绑定的网易云歌单ID
|
||||||
@ -29,6 +30,408 @@ type listinfo struct {
|
|||||||
ID int64 // 歌单绑定的歌曲ID
|
ID int64 // 歌单绑定的歌曲ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*****************************************************************/
|
||||||
|
/***************NeteaseCloudMusicApi框架API************************/
|
||||||
|
/*****************************************************************/
|
||||||
|
// 获取登陆信息
|
||||||
|
type keyInfo struct {
|
||||||
|
Data struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Unikey string `json:"unikey"`
|
||||||
|
} `json:"data"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
type cookyInfo struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Cookie string `json:"cookie"`
|
||||||
|
}
|
||||||
|
type qrInfo struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data struct {
|
||||||
|
Qrurl string `json:"qrurl"`
|
||||||
|
Qrimg string `json:"qrimg"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取歌单信息
|
||||||
|
type listInfoOfAPI struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
RelatedVideos interface{} `json:"relatedVideos"`
|
||||||
|
Playlist struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CoverImgID int64 `json:"coverImgId"`
|
||||||
|
CoverImgURL string `json:"coverImgUrl"`
|
||||||
|
CoverImgIDStr string `json:"coverImgId_str"`
|
||||||
|
AdType int `json:"adType"`
|
||||||
|
UserID int `json:"userId"`
|
||||||
|
CreateTime int64 `json:"createTime"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
OpRecommend bool `json:"opRecommend"`
|
||||||
|
HighQuality bool `json:"highQuality"`
|
||||||
|
NewImported bool `json:"newImported"`
|
||||||
|
UpdateTime int64 `json:"updateTime"`
|
||||||
|
TrackCount int `json:"trackCount"`
|
||||||
|
SpecialType int `json:"specialType"`
|
||||||
|
Privacy int `json:"privacy"`
|
||||||
|
TrackUpdateTime int64 `json:"trackUpdateTime"`
|
||||||
|
CommentThreadID string `json:"commentThreadId"`
|
||||||
|
PlayCount int `json:"playCount"`
|
||||||
|
TrackNumberUpdateTime int64 `json:"trackNumberUpdateTime"`
|
||||||
|
SubscribedCount int `json:"subscribedCount"`
|
||||||
|
CloudTrackCount int `json:"cloudTrackCount"`
|
||||||
|
Ordered bool `json:"ordered"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
UpdateFrequency interface{} `json:"updateFrequency"`
|
||||||
|
BackgroundCoverID int `json:"backgroundCoverId"`
|
||||||
|
BackgroundCoverURL interface{} `json:"backgroundCoverUrl"`
|
||||||
|
TitleImage int `json:"titleImage"`
|
||||||
|
TitleImageURL interface{} `json:"titleImageUrl"`
|
||||||
|
EnglishTitle interface{} `json:"englishTitle"`
|
||||||
|
OfficialPlaylistType interface{} `json:"officialPlaylistType"`
|
||||||
|
Subscribers []struct {
|
||||||
|
DefaultAvatar bool `json:"defaultAvatar"`
|
||||||
|
Province int `json:"province"`
|
||||||
|
AuthStatus int `json:"authStatus"`
|
||||||
|
Followed bool `json:"followed"`
|
||||||
|
AvatarURL string `json:"avatarUrl"`
|
||||||
|
AccountStatus int `json:"accountStatus"`
|
||||||
|
Gender int `json:"gender"`
|
||||||
|
City int `json:"city"`
|
||||||
|
Birthday int `json:"birthday"`
|
||||||
|
UserID int `json:"userId"`
|
||||||
|
UserType int `json:"userType"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
DetailDescription string `json:"detailDescription"`
|
||||||
|
AvatarImgID int64 `json:"avatarImgId"`
|
||||||
|
BackgroundImgID int64 `json:"backgroundImgId"`
|
||||||
|
BackgroundURL string `json:"backgroundUrl"`
|
||||||
|
Authority int `json:"authority"`
|
||||||
|
Mutual bool `json:"mutual"`
|
||||||
|
ExpertTags interface{} `json:"expertTags"`
|
||||||
|
Experts interface{} `json:"experts"`
|
||||||
|
DjStatus int `json:"djStatus"`
|
||||||
|
VipType int `json:"vipType"`
|
||||||
|
RemarkName interface{} `json:"remarkName"`
|
||||||
|
AuthenticationTypes int `json:"authenticationTypes"`
|
||||||
|
AvatarDetail interface{} `json:"avatarDetail"`
|
||||||
|
Anchor bool `json:"anchor"`
|
||||||
|
BackgroundImgIDStr string `json:"backgroundImgIdStr"`
|
||||||
|
AvatarImgIDStr string `json:"avatarImgIdStr"`
|
||||||
|
AvatarImgIDString string `json:"AvatarImgIDString"`
|
||||||
|
} `json:"subscribers"`
|
||||||
|
Subscribed interface{} `json:"subscribed"`
|
||||||
|
Creator struct {
|
||||||
|
DefaultAvatar bool `json:"defaultAvatar"`
|
||||||
|
Province int `json:"province"`
|
||||||
|
AuthStatus int `json:"authStatus"`
|
||||||
|
Followed bool `json:"followed"`
|
||||||
|
AvatarURL string `json:"avatarUrl"`
|
||||||
|
AccountStatus int `json:"accountStatus"`
|
||||||
|
Gender int `json:"gender"`
|
||||||
|
City int `json:"city"`
|
||||||
|
Birthday int `json:"birthday"`
|
||||||
|
UserID int `json:"userId"`
|
||||||
|
UserType int `json:"userType"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
DetailDescription string `json:"detailDescription"`
|
||||||
|
AvatarImgID int64 `json:"avatarImgId"`
|
||||||
|
BackgroundImgID int64 `json:"backgroundImgId"`
|
||||||
|
BackgroundURL string `json:"backgroundUrl"`
|
||||||
|
Authority int `json:"authority"`
|
||||||
|
Mutual bool `json:"mutual"`
|
||||||
|
ExpertTags interface{} `json:"expertTags"`
|
||||||
|
Experts interface{} `json:"experts"`
|
||||||
|
DjStatus int `json:"djStatus"`
|
||||||
|
VipType int `json:"vipType"`
|
||||||
|
RemarkName interface{} `json:"remarkName"`
|
||||||
|
AuthenticationTypes int `json:"authenticationTypes"`
|
||||||
|
AvatarDetail struct {
|
||||||
|
UserType int `json:"userType"`
|
||||||
|
IdentityLevel int `json:"identityLevel"`
|
||||||
|
IdentityIconURL string `json:"identityIconUrl"`
|
||||||
|
} `json:"avatarDetail"`
|
||||||
|
Anchor bool `json:"anchor"`
|
||||||
|
BackgroundImgIDStr string `json:"backgroundImgIdStr"`
|
||||||
|
AvatarImgIDStr string `json:"avatarImgIdStr"`
|
||||||
|
AvatarImgIDString string `json:"AvatarImgIDString"`
|
||||||
|
} `json:"creator"`
|
||||||
|
Tracks []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
Pst int `json:"pst"`
|
||||||
|
T int `json:"t"`
|
||||||
|
Ar []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Tns []interface{} `json:"tns"`
|
||||||
|
Alias []interface{} `json:"alias"`
|
||||||
|
} `json:"ar"`
|
||||||
|
Alia []string `json:"alia"`
|
||||||
|
Pop int `json:"pop"`
|
||||||
|
St int `json:"st"`
|
||||||
|
Rt string `json:"rt"`
|
||||||
|
Fee int `json:"fee"`
|
||||||
|
V int `json:"v"`
|
||||||
|
Crbt interface{} `json:"crbt"`
|
||||||
|
Cf string `json:"cf"`
|
||||||
|
Al struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PicURL string `json:"picUrl"`
|
||||||
|
Tns []interface{} `json:"tns"`
|
||||||
|
PicStr string `json:"pic_str"`
|
||||||
|
Pic int64 `json:"pic"`
|
||||||
|
} `json:"al"`
|
||||||
|
Dt int `json:"dt"`
|
||||||
|
H struct {
|
||||||
|
Br int `json:"br"`
|
||||||
|
Fid int `json:"fid"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Vd float64 `json:"vd"`
|
||||||
|
Sr int `json:"sr"`
|
||||||
|
} `json:"h"`
|
||||||
|
M struct {
|
||||||
|
Br int `json:"br"`
|
||||||
|
Fid int `json:"fid"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Vd float64 `json:"vd"`
|
||||||
|
Sr int `json:"sr"`
|
||||||
|
} `json:"m"`
|
||||||
|
L struct {
|
||||||
|
Br int `json:"br"`
|
||||||
|
Fid int `json:"fid"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Vd float64 `json:"vd"`
|
||||||
|
Sr int `json:"sr"`
|
||||||
|
} `json:"l"`
|
||||||
|
Sq interface{} `json:"sq"`
|
||||||
|
Hr interface{} `json:"hr"`
|
||||||
|
A interface{} `json:"a"`
|
||||||
|
Cd string `json:"cd"`
|
||||||
|
No int `json:"no"`
|
||||||
|
RtURL interface{} `json:"rtUrl"`
|
||||||
|
Ftype int `json:"ftype"`
|
||||||
|
RtUrls []interface{} `json:"rtUrls"`
|
||||||
|
DjID int `json:"djId"`
|
||||||
|
Copyright int `json:"copyright"`
|
||||||
|
SID int `json:"s_id"`
|
||||||
|
Mark int `json:"mark"`
|
||||||
|
OriginCoverType int `json:"originCoverType"`
|
||||||
|
OriginSongSimpleData interface{} `json:"originSongSimpleData"`
|
||||||
|
TagPicList interface{} `json:"tagPicList"`
|
||||||
|
ResourceState bool `json:"resourceState"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
SongJumpInfo interface{} `json:"songJumpInfo"`
|
||||||
|
EntertainmentTags interface{} `json:"entertainmentTags"`
|
||||||
|
Single int `json:"single"`
|
||||||
|
NoCopyrightRcmd interface{} `json:"noCopyrightRcmd"`
|
||||||
|
Alg interface{} `json:"alg"`
|
||||||
|
Rtype int `json:"rtype"`
|
||||||
|
Rurl interface{} `json:"rurl"`
|
||||||
|
Mst int `json:"mst"`
|
||||||
|
Cp int `json:"cp"`
|
||||||
|
Mv int `json:"mv"`
|
||||||
|
PublishTime int64 `json:"publishTime"`
|
||||||
|
Tns []string `json:"tns,omitempty"`
|
||||||
|
} `json:"tracks"`
|
||||||
|
VideoIds interface{} `json:"videoIds"`
|
||||||
|
Videos interface{} `json:"videos"`
|
||||||
|
TrackIds []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
V int `json:"v"`
|
||||||
|
T int `json:"t"`
|
||||||
|
At int64 `json:"at"`
|
||||||
|
Alg interface{} `json:"alg"`
|
||||||
|
UID int `json:"uid"`
|
||||||
|
RcmdReason string `json:"rcmdReason"`
|
||||||
|
Sc interface{} `json:"sc"`
|
||||||
|
Lr int `json:"lr,omitempty"`
|
||||||
|
} `json:"trackIds"`
|
||||||
|
ShareCount int `json:"shareCount"`
|
||||||
|
CommentCount int `json:"commentCount"`
|
||||||
|
RemixVideo interface{} `json:"remixVideo"`
|
||||||
|
SharedUsers interface{} `json:"sharedUsers"`
|
||||||
|
HistorySharedUsers interface{} `json:"historySharedUsers"`
|
||||||
|
GradeStatus string `json:"gradeStatus"`
|
||||||
|
Score interface{} `json:"score"`
|
||||||
|
AlgTags interface{} `json:"algTags"`
|
||||||
|
} `json:"playlist"`
|
||||||
|
Urls interface{} `json:"urls"`
|
||||||
|
Privileges []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Fee int `json:"fee"`
|
||||||
|
Payed int `json:"payed"`
|
||||||
|
RealPayed int `json:"realPayed"`
|
||||||
|
St int `json:"st"`
|
||||||
|
Pl int `json:"pl"`
|
||||||
|
Dl int `json:"dl"`
|
||||||
|
Sp int `json:"sp"`
|
||||||
|
Cp int `json:"cp"`
|
||||||
|
Subp int `json:"subp"`
|
||||||
|
Cs bool `json:"cs"`
|
||||||
|
Maxbr int `json:"maxbr"`
|
||||||
|
Fl int `json:"fl"`
|
||||||
|
Pc interface{} `json:"pc"`
|
||||||
|
Toast bool `json:"toast"`
|
||||||
|
Flag int `json:"flag"`
|
||||||
|
PaidBigBang bool `json:"paidBigBang"`
|
||||||
|
PreSell bool `json:"preSell"`
|
||||||
|
PlayMaxbr int `json:"playMaxbr"`
|
||||||
|
DownloadMaxbr int `json:"downloadMaxbr"`
|
||||||
|
MaxBrLevel string `json:"maxBrLevel"`
|
||||||
|
PlayMaxBrLevel string `json:"playMaxBrLevel"`
|
||||||
|
DownloadMaxBrLevel string `json:"downloadMaxBrLevel"`
|
||||||
|
PlLevel string `json:"plLevel"`
|
||||||
|
DlLevel string `json:"dlLevel"`
|
||||||
|
FlLevel string `json:"flLevel"`
|
||||||
|
Rscl int `json:"rscl"`
|
||||||
|
FreeTrialPrivilege struct {
|
||||||
|
ResConsumable bool `json:"resConsumable"`
|
||||||
|
UserConsumable bool `json:"userConsumable"`
|
||||||
|
ListenType interface{} `json:"listenType"`
|
||||||
|
} `json:"freeTrialPrivilege"`
|
||||||
|
ChargeInfoList []struct {
|
||||||
|
Rate int `json:"rate"`
|
||||||
|
ChargeURL interface{} `json:"chargeUrl"`
|
||||||
|
ChargeMessage interface{} `json:"chargeMessage"`
|
||||||
|
ChargeType int `json:"chargeType"`
|
||||||
|
} `json:"chargeInfoList"`
|
||||||
|
} `json:"privileges"`
|
||||||
|
SharedPrivilege interface{} `json:"sharedPrivilege"`
|
||||||
|
ResEntrance interface{} `json:"resEntrance"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取歌单列表
|
||||||
|
type musicListOfApI struct {
|
||||||
|
Songs []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
Pst int `json:"pst"`
|
||||||
|
T int `json:"t"`
|
||||||
|
Ar []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Tns []interface{} `json:"tns"`
|
||||||
|
Alias []interface{} `json:"alias"`
|
||||||
|
} `json:"ar"`
|
||||||
|
Alia []string `json:"alia"`
|
||||||
|
Pop int `json:"pop"`
|
||||||
|
St int `json:"st"`
|
||||||
|
Rt string `json:"rt"`
|
||||||
|
Fee int `json:"fee"`
|
||||||
|
V int `json:"v"`
|
||||||
|
Crbt interface{} `json:"crbt"`
|
||||||
|
Cf string `json:"cf"`
|
||||||
|
Al struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PicURL string `json:"picUrl"`
|
||||||
|
Tns []interface{} `json:"tns"`
|
||||||
|
PicStr string `json:"pic_str"`
|
||||||
|
Pic int64 `json:"pic"`
|
||||||
|
} `json:"al"`
|
||||||
|
Dt int `json:"dt"`
|
||||||
|
H struct {
|
||||||
|
Br int `json:"br"`
|
||||||
|
Fid int `json:"fid"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Vd float32 `json:"vd"`
|
||||||
|
Sr int `json:"sr"`
|
||||||
|
} `json:"h"`
|
||||||
|
M struct {
|
||||||
|
Br int `json:"br"`
|
||||||
|
Fid int `json:"fid"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Vd float32 `json:"vd"`
|
||||||
|
Sr int `json:"sr"`
|
||||||
|
} `json:"m"`
|
||||||
|
L struct {
|
||||||
|
Br int `json:"br"`
|
||||||
|
Fid int `json:"fid"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Vd float32 `json:"vd"`
|
||||||
|
Sr int `json:"sr"`
|
||||||
|
} `json:"l"`
|
||||||
|
Sq interface{} `json:"sq"`
|
||||||
|
Hr interface{} `json:"hr"`
|
||||||
|
A interface{} `json:"a"`
|
||||||
|
Cd string `json:"cd"`
|
||||||
|
No int `json:"no"`
|
||||||
|
RtURL interface{} `json:"rtUrl"`
|
||||||
|
Ftype int `json:"ftype"`
|
||||||
|
RtUrls []interface{} `json:"rtUrls"`
|
||||||
|
DjID int `json:"djId"`
|
||||||
|
Copyright int `json:"copyright"`
|
||||||
|
SID int `json:"s_id"`
|
||||||
|
Mark int `json:"mark"`
|
||||||
|
OriginCoverType int `json:"originCoverType"`
|
||||||
|
OriginSongSimpleData interface{} `json:"originSongSimpleData"`
|
||||||
|
TagPicList interface{} `json:"tagPicList"`
|
||||||
|
ResourceState bool `json:"resourceState"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
SongJumpInfo interface{} `json:"songJumpInfo"`
|
||||||
|
EntertainmentTags interface{} `json:"entertainmentTags"`
|
||||||
|
AwardTags interface{} `json:"awardTags"`
|
||||||
|
Single int `json:"single"`
|
||||||
|
NoCopyrightRcmd interface{} `json:"noCopyrightRcmd"`
|
||||||
|
Rtype int `json:"rtype"`
|
||||||
|
Rurl interface{} `json:"rurl"`
|
||||||
|
Mst int `json:"mst"`
|
||||||
|
Cp int `json:"cp"`
|
||||||
|
Mv int `json:"mv"`
|
||||||
|
PublishTime int64 `json:"publishTime"`
|
||||||
|
Tns []string `json:"tns,omitempty"`
|
||||||
|
} `json:"songs"`
|
||||||
|
Privileges []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Fee int `json:"fee"`
|
||||||
|
Payed int `json:"payed"`
|
||||||
|
St int `json:"st"`
|
||||||
|
Pl int `json:"pl"`
|
||||||
|
Dl int `json:"dl"`
|
||||||
|
Sp int `json:"sp"`
|
||||||
|
Cp int `json:"cp"`
|
||||||
|
Subp int `json:"subp"`
|
||||||
|
Cs bool `json:"cs"`
|
||||||
|
Maxbr int `json:"maxbr"`
|
||||||
|
Fl int `json:"fl"`
|
||||||
|
Toast bool `json:"toast"`
|
||||||
|
Flag int `json:"flag"`
|
||||||
|
PreSell bool `json:"preSell"`
|
||||||
|
PlayMaxbr int `json:"playMaxbr"`
|
||||||
|
DownloadMaxbr int `json:"downloadMaxbr"`
|
||||||
|
MaxBrLevel string `json:"maxBrLevel"`
|
||||||
|
PlayMaxBrLevel string `json:"playMaxBrLevel"`
|
||||||
|
DownloadMaxBrLevel string `json:"downloadMaxBrLevel"`
|
||||||
|
PlLevel string `json:"plLevel"`
|
||||||
|
DlLevel string `json:"dlLevel"`
|
||||||
|
FlLevel string `json:"flLevel"`
|
||||||
|
Rscl int `json:"rscl"`
|
||||||
|
FreeTrialPrivilege struct {
|
||||||
|
ResConsumable bool `json:"resConsumable"`
|
||||||
|
UserConsumable bool `json:"userConsumable"`
|
||||||
|
ListenType interface{} `json:"listenType"`
|
||||||
|
} `json:"freeTrialPrivilege"`
|
||||||
|
ChargeInfoList []struct {
|
||||||
|
Rate int `json:"rate"`
|
||||||
|
ChargeURL interface{} `json:"chargeUrl"`
|
||||||
|
ChargeMessage interface{} `json:"chargeMessage"`
|
||||||
|
ChargeType int `json:"chargeType"`
|
||||||
|
} `json:"chargeInfoList"`
|
||||||
|
} `json:"privileges"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************/
|
||||||
|
/*********************独角兽API随机抽歌信息**************************/
|
||||||
|
/*****************************************************************/
|
||||||
// 独角兽API随机抽歌信息
|
// 独角兽API随机抽歌信息
|
||||||
type ovooaData struct {
|
type ovooaData struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
|
|||||||
@ -470,6 +470,10 @@ func init() {
|
|||||||
uid := ctx.Event.UserID
|
uid := ctx.Event.UserID
|
||||||
fiancee := ctx.State["regex_matched"].([]string)
|
fiancee := ctx.State["regex_matched"].([]string)
|
||||||
gay, _ := strconv.ParseInt(fiancee[2]+fiancee[3], 10, 64)
|
gay, _ := strconv.ParseInt(fiancee[2]+fiancee[3], 10, 64)
|
||||||
|
if gay == uid {
|
||||||
|
ctx.Send(message.ReplyWithMessage(message.At(uid), message.Text("[qqwife]你想给自己买什么礼物呢?")))
|
||||||
|
return
|
||||||
|
}
|
||||||
// 获取CD
|
// 获取CD
|
||||||
cdTime, err := 民政局.getCDtime(gid)
|
cdTime, err := 民政局.getCDtime(gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user