mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-19 05:30:07 +08:00
803 lines
25 KiB
Go
803 lines
25 KiB
Go
// Package guessmusic 基于zbp的猜歌插件
|
||
package guessmusic
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"io/fs"
|
||
"io/ioutil"
|
||
"math/rand"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"os/exec"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/sirupsen/logrus"
|
||
"github.com/wdvxdr1123/ZeroBot/utils/helper"
|
||
|
||
"github.com/pkg/errors"
|
||
zero "github.com/wdvxdr1123/ZeroBot"
|
||
"github.com/wdvxdr1123/ZeroBot/message"
|
||
|
||
"github.com/FloatTech/floatbox/file"
|
||
"github.com/FloatTech/floatbox/web"
|
||
ctrl "github.com/FloatTech/zbpctrl"
|
||
"github.com/FloatTech/zbputils/control"
|
||
"github.com/FloatTech/zbputils/ctxext"
|
||
"github.com/wdvxdr1123/ZeroBot/extension/single"
|
||
|
||
// 图片输出
|
||
|
||
"github.com/FloatTech/zbputils/img/text"
|
||
)
|
||
|
||
const (
|
||
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
|
||
)
|
||
|
||
var (
|
||
catlist = make(map[string]int64, 100)
|
||
filelist []string
|
||
musictypelist = "mp3;MP3;wav;WAV;amr;AMR;3gp;3GP;3gpp;3GPP;acc;ACC"
|
||
cuttime = [...]string{"00:00:05", "00:00:30", "00:01:00"} // 音乐切割时间点,可自行调节时间(时:分:秒)
|
||
cfg config
|
||
)
|
||
|
||
func init() { // 插件主体
|
||
engine := control.Register("guessmusic", &ctrl.Options[*zero.Ctx]{
|
||
DisableOnDefault: false,
|
||
Help: "猜歌插件(该插件依赖ffmpeg)\n" +
|
||
"------bot主人指令------\n" +
|
||
"- 设置猜歌缓存歌库路径 [绝对路径]\n" +
|
||
"- 设置猜歌[本地/Api] [true/false]\n" +
|
||
"- 登录网易云\n" +
|
||
"- 添加歌单 [网易云歌单ID] [歌单名称]\n" +
|
||
"- 删除歌单 [网易云歌单ID/API歌单名称]\n" +
|
||
"注:\n1.不登陆也能用,API有几率返回400\n" +
|
||
"2.[歌单名称]可为空,默认原标题\n" +
|
||
"------公 用 指 令------\n" +
|
||
"- 获取歌单列表\n" +
|
||
"- [网易云歌单ID/API歌单名称]歌单信息\n" +
|
||
"- [个人/团队]猜歌\n" +
|
||
"注:默认歌库为网易云ACG动画榜\n" +
|
||
"可在后面添加[-歌单名称]进行指定歌单猜歌\n" +
|
||
"歌单的歌曲命名规则为:\n歌名 - 歌手 - 其他(歌曲出处之类)",
|
||
PrivateDataFolder: "guessmusic",
|
||
}).ApplySingle(single.New(
|
||
single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }),
|
||
single.WithPostFn[int64](func(ctx *zero.Ctx) {
|
||
ctx.Send(
|
||
message.ReplyWithMessage(ctx.Event.MessageID,
|
||
message.Text("已经有正在进行的游戏..."),
|
||
),
|
||
)
|
||
}),
|
||
))
|
||
cachePath := engine.DataFolder() + "cache/"
|
||
err := os.MkdirAll(cachePath, 0755)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
cfgFile := engine.DataFolder() + "config.json"
|
||
if file.IsExist(cfgFile) {
|
||
reader, err := os.Open(cfgFile)
|
||
if err == nil {
|
||
err = json.NewDecoder(reader).Decode(&cfg)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
} else {
|
||
panic(err)
|
||
}
|
||
err = reader.Close()
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
} else {
|
||
var plist = []listRaw{
|
||
{
|
||
Name: "动画榜",
|
||
ID: 3001835560,
|
||
},
|
||
}
|
||
cfg = config{ // 默认 config
|
||
MusicPath: file.BOTPATH + "/data/guessmusic/music/", // 绝对路径,歌库根目录,通过指令进行更改
|
||
Local: true, // 是否使用本地音乐库
|
||
API: true, // 是否使用 Api
|
||
Cookie: "",
|
||
Playlist: plist,
|
||
}
|
||
err = saveConfig(cfgFile)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
}
|
||
err = getcatlist(cfg.MusicPath)
|
||
if err != nil {
|
||
logrus.Infof("[guessmusic2]无法获取歌单列表,[error]:%s", err)
|
||
}
|
||
engine.OnRegex(`^设置猜歌(缓存歌库路径|本地|Api)\s*(.*)$`, func(ctx *zero.Ctx) bool {
|
||
if !zero.SuperUserPermission(ctx) {
|
||
ctx.SendChain(message.Text("只有bot主人可以设置!"))
|
||
return false
|
||
}
|
||
return true
|
||
}).SetBlock(true).
|
||
Handle(func(ctx *zero.Ctx) {
|
||
option := ctx.State["regex_matched"].([]string)[1]
|
||
value := ctx.State["regex_matched"].([]string)[2]
|
||
switch option {
|
||
case "缓存歌库路径":
|
||
if value == "" {
|
||
ctx.SendChain(message.Text("请输入正确的路径!"))
|
||
return
|
||
}
|
||
musicPath := strings.ReplaceAll(value, "\\", "/")
|
||
if !strings.HasSuffix(musicPath, "/") {
|
||
musicPath += "/"
|
||
}
|
||
err = os.MkdirAll(cfg.MusicPath, 0755)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("[生成文件夹错误]ERROR:", err))
|
||
return
|
||
}
|
||
cfg.MusicPath = musicPath
|
||
case "本地":
|
||
choice, err := strconv.ParseBool(value)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("ERROR:", err))
|
||
return
|
||
}
|
||
cfg.Local = choice
|
||
case "Api":
|
||
choice, err := strconv.ParseBool(value)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("ERROR:", err))
|
||
return
|
||
}
|
||
cfg.API = choice
|
||
}
|
||
err = saveConfig(cfgFile)
|
||
if err == nil {
|
||
ctx.SendChain(message.Text("成功!"))
|
||
} else {
|
||
ctx.SendChain(message.Text("ERROR:", 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 := "https://music.cyrilstudio.top/login/qr/key"
|
||
data, err := web.GetData(keyURL)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("获取网易云key失败, ERROR:", err))
|
||
return
|
||
}
|
||
var keyInfo keyInfo
|
||
err = json.Unmarshal(data, &keyInfo)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("解析网易云key失败, ERROR:", err))
|
||
return
|
||
}
|
||
qrURL := "https://music.cyrilstudio.top/login/qr/create?key=" + keyInfo.Data.Unikey + "&qrimg=1"
|
||
data, err = web.GetData(qrURL)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("获取网易云二维码失败, ERROR:", err))
|
||
return
|
||
}
|
||
var qrInfo qrInfo
|
||
err = json.Unmarshal(data, &qrInfo)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("解析网易云二维码失败, ERROR:", 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 := "https://music.cyrilstudio.top/login/qr/check?key=" + url.QueryEscape(keyInfo.Data.Unikey)
|
||
referer := "https://music.cyrilstudio.top"
|
||
data, err := web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("无法获取登录状态, ERROR:", err))
|
||
return
|
||
}
|
||
var cookiesInfo cookyInfo
|
||
err = json.Unmarshal(data, &cookiesInfo)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("解析登录状态失败, ERROR:", 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("ERROR:", 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?(\d+)(\s(.*))?$`, zero.SuperUserPermission).SetBlock(true).Limit(ctxext.LimitByGroup).
|
||
Handle(func(ctx *zero.Ctx) {
|
||
listID := ctx.State["regex_matched"].([]string)[1]
|
||
listName := ctx.State["regex_matched"].([]string)[3]
|
||
ctx.SendChain(message.Text("正在校验歌单信息,请稍等"))
|
||
// 是否存在该歌单
|
||
apiURL := "https://music.cyrilstudio.top/playlist/detail?id=" + listID + "&cookie=" + cfg.Cookie
|
||
referer := "https://music.cyrilstudio.top"
|
||
data, err := web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("无法连接歌单,[error]", err))
|
||
return
|
||
}
|
||
var parsed topList
|
||
err = json.Unmarshal(data, &parsed)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("无法解析歌单ID内容,[error]", err))
|
||
return
|
||
}
|
||
// 是否有权限访问歌单列表内容
|
||
apiURL = "https://music.cyrilstudio.top/playlist/track/all?id=" + listID + "&cookie=" + cfg.Cookie
|
||
referer = "https://music.163.com/"
|
||
data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("无法获取歌单列表\n ERROR:", err))
|
||
return
|
||
}
|
||
var musiclist topMusicInfo
|
||
err = json.Unmarshal(data, &musiclist)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("你的cookie在API中无权访问该歌单\n该歌单有可能是用户私人歌单"))
|
||
return
|
||
}
|
||
// 获取列表名字
|
||
if listName == "" {
|
||
listName = parsed.Playlist.Name
|
||
}
|
||
playID, _ := strconv.ParseInt(listID, 10, 64)
|
||
catlist[listName] = playID
|
||
cfg.Playlist = append(cfg.Playlist, listRaw{
|
||
Name: listName,
|
||
ID: playID,
|
||
})
|
||
err = saveConfig(cfgFile)
|
||
if err == nil {
|
||
ctx.SendChain(message.Text("成功!"))
|
||
} else {
|
||
ctx.SendChain(message.Text("ERROR:", err))
|
||
}
|
||
})
|
||
engine.OnRegex(`^删除歌单\s?(.*)$`, zero.SuperUserPermission).SetBlock(true).Limit(ctxext.LimitByGroup).
|
||
Handle(func(ctx *zero.Ctx) {
|
||
delList := ctx.State["regex_matched"].([]string)[1]
|
||
var playlist []listRaw
|
||
var newCatList = make(map[string]int64)
|
||
var ok = false
|
||
for name, musicID := range catlist {
|
||
if delList == name || delList == strconv.FormatInt(musicID, 10) {
|
||
ok = true
|
||
continue
|
||
}
|
||
newCatList[name] = musicID
|
||
playlist = append(playlist, listRaw{
|
||
Name: name,
|
||
ID: musicID,
|
||
})
|
||
}
|
||
if !ok {
|
||
ctx.SendChain(message.Text("目标歌单未找到,请确认是否正确"))
|
||
return
|
||
}
|
||
catlist = newCatList
|
||
cfg.Playlist = playlist
|
||
err = saveConfig(cfgFile)
|
||
if err == nil {
|
||
ctx.SendChain(message.Text("成功!"))
|
||
} else {
|
||
ctx.SendChain(message.Text("ERROR:", err))
|
||
}
|
||
})
|
||
engine.OnFullMatch("获取歌单列表").SetBlock(true).Limit(ctxext.LimitByGroup).
|
||
Handle(func(ctx *zero.Ctx) {
|
||
var msg []string
|
||
// 获取网易云歌单列表
|
||
if cfg.API {
|
||
catlist = make(map[string]int64, 100)
|
||
msg = append(msg, "当前添加的API歌单含有以下:\n")
|
||
for i, listInfo := range cfg.Playlist {
|
||
catlist[listInfo.Name] = listInfo.ID
|
||
msg = append(msg, strconv.Itoa(i)+":"+listInfo.Name)
|
||
if i%3 == 2 {
|
||
msg = append(msg, "\n")
|
||
}
|
||
}
|
||
}
|
||
// 获取本地歌单列表*/
|
||
if cfg.Local {
|
||
err = os.MkdirAll(cfg.MusicPath, 0755)
|
||
if err == nil {
|
||
files, err := ioutil.ReadDir(cfg.MusicPath)
|
||
if err == nil {
|
||
if len(files) == 0 {
|
||
ctx.SendChain(message.Text("缓存目录没有读取到任何歌单"))
|
||
filelist = nil
|
||
} else {
|
||
msg = append(msg, "\n当前本地歌单含有以下:\n")
|
||
i := 0
|
||
for _, name := range files {
|
||
if !name.IsDir() {
|
||
continue
|
||
}
|
||
filelist[i] = strconv.Itoa(i) + ":" + name.Name()
|
||
msg = append(msg, filelist[i])
|
||
if i%3 == 2 {
|
||
msg = append(msg, "\n")
|
||
}
|
||
i++
|
||
}
|
||
}
|
||
} else {
|
||
ctx.SendChain(message.Text("[读取本地列表错误]ERROR:", err))
|
||
}
|
||
} else {
|
||
ctx.SendChain(message.Text("[生成文件夹错误]ERROR:", err))
|
||
}
|
||
}
|
||
if msg == nil {
|
||
ctx.SendChain(message.Text("本地和API均未开启!"))
|
||
return
|
||
}
|
||
msgs, err := text.RenderToBase64(strings.Join(msg, " "), text.FontFile, 400, 20)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("生成列表图片失败,请重试"))
|
||
return
|
||
}
|
||
if id := ctx.SendChain(message.Image("base64://" + helper.BytesToString(msgs))); id.ID() == 0 {
|
||
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
|
||
}
|
||
})
|
||
engine.OnSuffix("歌单信息").SetBlock(true).Limit(ctxext.LimitByGroup).
|
||
Handle(func(ctx *zero.Ctx) {
|
||
list := ctx.State["args"].(string)
|
||
if list == "" {
|
||
ctx.SendChain(message.Text("请输入歌单ID或者API歌单名称\n歌单ID为(网页/分享)链接的“playlist”后面的第一串数字"))
|
||
return
|
||
}
|
||
var listIDStr string
|
||
for listName, listID := range catlist {
|
||
if list == listName || list == strconv.FormatInt(listID, 10) {
|
||
listIDStr = strconv.FormatInt(listID, 10)
|
||
break
|
||
}
|
||
}
|
||
if listIDStr == "" {
|
||
_, err := strconv.ParseInt(list, 10, 64)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("仅支持歌单ID查询"))
|
||
return
|
||
}
|
||
listIDStr = list
|
||
}
|
||
apiURL := "https://music.cyrilstudio.top/playlist/detail?id=" + listIDStr + "&cookie=" + cfg.Cookie
|
||
referer := "https://music.cyrilstudio.top"
|
||
data, err := web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("无法连接歌单,[error]", err))
|
||
return
|
||
}
|
||
var parsed topList
|
||
err = json.Unmarshal(data, &parsed)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text("无法解析歌单ID内容,[error]", 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"),
|
||
))
|
||
})
|
||
engine.OnRegex(`^(个人|团队)猜歌(-(.*))?$`, zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByGroup).
|
||
Handle(func(ctx *zero.Ctx) {
|
||
mode := ctx.State["regex_matched"].([]string)[3]
|
||
if mode == "" {
|
||
mode = "动画榜"
|
||
catlist[mode] = 3001835560
|
||
}
|
||
_, ok := catlist[mode]
|
||
// 如果本地和API不存在该歌单
|
||
if !strings.Contains(strings.Join(filelist, " "), mode) && !ok {
|
||
ctx.SendChain(message.Text("歌单名称错误,可以发送“获取歌单列表”获取歌单名称"))
|
||
return
|
||
}
|
||
gid := strconv.FormatInt(ctx.Event.GroupID, 10)
|
||
ctx.SendChain(message.Text("正在准备歌曲,请稍等\n回答“-[歌曲信息(歌名歌手等)|提示|取消]”\n一共3段语音,6次机会"))
|
||
// 随机抽歌
|
||
musicName, pathOfMusic, err := musicLottery(mode, cfg.MusicPath)
|
||
if err != nil {
|
||
ctx.SendChain(message.Text(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 + gid + "/"
|
||
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)))
|
||
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)))
|
||
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)))
|
||
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)))
|
||
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)))
|
||
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 saveConfig(cfgFile string) (err error) {
|
||
if reader, err := os.Create(cfgFile); err == nil {
|
||
err = json.NewEncoder(reader).Encode(&cfg)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
} else {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func getcatlist(pathOfMusic string) error {
|
||
catlist = make(map[string]int64, 100)
|
||
for _, listInfo := range cfg.Playlist {
|
||
catlist[listInfo.Name] = listInfo.ID
|
||
}
|
||
err := os.MkdirAll(pathOfMusic, 0755)
|
||
if err != nil {
|
||
err = errors.Errorf("[生成文件夹错误]ERROR:%s", err)
|
||
return err
|
||
}
|
||
files, err := ioutil.ReadDir(pathOfMusic)
|
||
if err != nil {
|
||
err = errors.Errorf("[读取本地列表错误]ERROR:%s", err)
|
||
return err
|
||
}
|
||
for i, name := range files {
|
||
filelist = append(filelist, strconv.Itoa(i)+":"+name.Name())
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// 随机抽取音乐
|
||
func musicLottery(mode, musicPath string) (musicName, pathOfMusic string, err error) {
|
||
pathOfMusic = musicPath + mode + "/"
|
||
err = os.MkdirAll(pathOfMusic, 0755)
|
||
if err != nil {
|
||
err = errors.Errorf("[生成文件夹错误]ERROR:%s", err)
|
||
return
|
||
}
|
||
files, err := ioutil.ReadDir(pathOfMusic)
|
||
if err != nil {
|
||
err = errors.Errorf("[读取本地列表错误]ERROR:%s", err)
|
||
return
|
||
}
|
||
listID, ok := catlist[mode]
|
||
listIDstr := strconv.FormatInt(listID, 10)
|
||
if cfg.Local && cfg.API {
|
||
switch {
|
||
case len(files) == 0:
|
||
if !ok {
|
||
// 如果歌单是本地歌单
|
||
err = errors.New("本地歌单数据为0")
|
||
return
|
||
}
|
||
// 如果没有任何本地就下载歌曲
|
||
musicName, err = getListMusic(listIDstr, pathOfMusic)
|
||
if err != nil {
|
||
err = errors.Errorf("[本地数据为0,歌曲下载错误]ERROR:%s", err)
|
||
return
|
||
}
|
||
case rand.Intn(2) == 0 || !ok:
|
||
// 1/2概率抽本地或者歌单只有本地有时
|
||
musicName = getLocalMusic(files)
|
||
default:
|
||
musicName, err = getListMusic(listIDstr, pathOfMusic)
|
||
if err != nil {
|
||
// 如果下载失败就从本地抽一个歌曲
|
||
musicName = getLocalMusic(files)
|
||
err = nil
|
||
}
|
||
}
|
||
return
|
||
}
|
||
if cfg.Local {
|
||
if len(files) == 0 {
|
||
err = errors.New("[本地数据为0,未开启API数据]")
|
||
return
|
||
}
|
||
musicName = getLocalMusic(files)
|
||
return
|
||
}
|
||
if cfg.API && ok {
|
||
musicName, err = getListMusic(listIDstr, pathOfMusic)
|
||
if err != nil {
|
||
err = errors.Errorf("[获取API失败,未开启本地数据] ERROR:%s", err)
|
||
return
|
||
}
|
||
return
|
||
}
|
||
err = errors.New("[请确认本地和API设置已开启或歌单存在]")
|
||
return
|
||
}
|
||
|
||
func getLocalMusic(files []fs.FileInfo) (musicName string) {
|
||
if len(files) > 1 {
|
||
musicName = files[rand.Intn(len(files))].Name()
|
||
} else {
|
||
musicName = files[0].Name()
|
||
}
|
||
return
|
||
}
|
||
|
||
// 下载网易云歌单音乐
|
||
func getListMusic(listID, pathOfMusic string) (musicName string, err error) {
|
||
apiURL := "https://music.cyrilstudio.top/playlist/track/all?id=" + listID + "&cookie=" + cfg.Cookie
|
||
referer := "https://music.163.com/"
|
||
data, err := web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
|
||
if err != nil {
|
||
err = errors.Errorf("无法获取歌单列表\n ERROR: %s", err)
|
||
return
|
||
}
|
||
var parsed topMusicInfo
|
||
err = json.Unmarshal(data, &parsed)
|
||
if err != nil {
|
||
err = errors.Errorf("无法读取歌单列表\n ERROR: %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
|
||
}
|
||
musicURL := "http://music.163.com/song/media/outer/url?id=" + strconv.Itoa(musicID)
|
||
response, err := http.Head(musicURL)
|
||
if err != nil {
|
||
err = errors.Errorf("下载音乐失败, ERROR: %s", err)
|
||
return
|
||
}
|
||
_ = response.Body.Close()
|
||
if response.StatusCode != 200 {
|
||
err = errors.Errorf("下载音乐失败, Status Code: %d", response.StatusCode)
|
||
return
|
||
}
|
||
if cource != "" {
|
||
musicName = name + " - " + artistName + " - " + cource + ".mp3"
|
||
} else {
|
||
musicName = name + " - " + artistName + ".mp3"
|
||
}
|
||
downMusic := pathOfMusic + musicName
|
||
if file.IsNotExist(downMusic) {
|
||
data, err = web.GetData(musicURL)
|
||
if err != nil {
|
||
return
|
||
}
|
||
err = os.WriteFile(downMusic, data, 0666)
|
||
if err != nil {
|
||
return
|
||
}
|
||
}
|
||
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
|
||
}
|