全新的guessmusic插件 (#499)

This commit is contained in:
方柳煜 2022-11-13 18:01:15 +08:00 committed by GitHub
parent 1c93611a16
commit 743e5509a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1542 additions and 607 deletions

View File

@ -734,37 +734,32 @@ print("run[CQ:image,file="+j["img"]+"]")
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/guessmusic"`
- 猜歌插件该插件依赖ffmpeg
- 因为API不可抗因素更改为了本地猜歌但仍支持歌曲下载VIP歌曲无法下载黑胶可以
猜歌插件该插件依赖ffmpeg
---------主 人 指 令---------
- [x] 设置猜歌歌库路径 [绝对路径]
- [x] 猜歌[开启/关闭][歌单/歌词]自动下载
- 现只有歌词指令有效
- [ ] 添加歌单 [网易云歌单链接/ID] [歌单名称]
- [x] 下载歌曲 [歌曲名称/网易云歌曲ID] [歌单名称]
- [x] 删除歌单 [网易云歌单ID/歌单名称]
- 注删除网易云歌单ID仅只是解除绑定删除歌单名称是将本地数据全部删除
- [x] [创建/删除]歌单 [歌单名称]
- [x] 下载歌曲[歌曲名称/网易云歌曲ID]到[歌单名称]
-------管 理 员 指 令--------
- [x] 设置猜歌默认歌单 [歌单名称]
- [x] 上传歌曲[群文件的音乐名]到[歌单名称]
------公 用 指 令------
- [x] 歌单列表
- [x] [个人/团队]猜歌
- 注:默认歌库为歌单列表第一个,如果设置了默认歌单变为指定的歌单
- 可在“[个人/团队]猜歌指令”后面添加[-歌单名称]进行指定歌单猜歌
- 猜歌内容必须以[-]开头才会识别
- 本地歌曲命名规则为:\n歌名 - 歌手 - 其他(歌曲出处之类)
------插 件 扩 展------
NeteaseCloudMusicApi项目地址:https://binaryify.github.io/NeteaseCloudMusicApi/#/
- [x] 设置猜歌API帮助
- [x] 设置猜歌API [API首页网址]
- [x] 猜歌[开启/关闭][歌单/歌词]自动下载
- [ ] 登录网易云
- [x] 歌单信息 [网易云歌单链接/ID]
- [x] [歌单名称]绑定网易云[网易云歌单链接/ID]
- [x] 下载歌单[网易云歌单链接/ID]到[歌单名称]
- [x] 解除绑定 [歌单名称]
</details>
<details>

View 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
}

View 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

View File

@ -3,11 +3,12 @@ package guessmusic
// config内容
type config struct {
MusicPath string `json:"musicPath"`
APIURL string `json:"apiURL"`
Playlist []listRaw `json:"playlist"`
Defaultlist []dlist `json:"defaultlist"`
Local bool `json:"local"`
API bool `json:"api"`
Cookie string `json:"cookie"`
Playlist []listRaw `json:"playlist"`
Defaultlist []dlist `json:"defaultlist"`
}
// 记录歌单绑定的网易云歌单ID
@ -29,6 +30,408 @@ type listinfo struct {
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随机抽歌信息
type ovooaData struct {
Code int `json:"code"`

View File

@ -470,6 +470,10 @@ func init() {
uid := ctx.Event.UserID
fiancee := ctx.State["regex_matched"].([]string)
gay, _ := strconv.ParseInt(fiancee[2]+fiancee[3], 10, 64)
if gay == uid {
ctx.Send(message.ReplyWithMessage(message.At(uid), message.Text("[qqwife]你想给自己买什么礼物呢?")))
return
}
// 获取CD
cdTime, err := 民政局.getCDtime(gid)
if err != nil {