ZeroBot-Plugin/plugin/guessmusic/main.go
github-actions[bot] 15a5b347e8 🎨 改进代码样式
2022-06-29 12:58:24 +00:00

476 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

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

// Package guessmusic 基于zbp的猜歌插件
package guessmusic
import (
"bytes"
"io/ioutil"
"math/rand"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
control "github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/web"
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"github.com/wdvxdr1123/ZeroBot/extension/single"
)
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 (
musicPath = file.BOTPATH + "/data/guessmusic/music/" // 绝对路径,歌库根目录,通过指令进行更改
cuttime = [...]string{"00:00:05", "00:00:30", "00:01:00"} // 音乐切割时间点,可自行调节时间(时:分:秒)
)
func init() { // 插件主体
engine := control.Register("guessmusic", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "猜歌插件该插件依赖ffmpeg\n" +
"- 个人猜歌\n" +
"- 团队猜歌\n" +
"- 设置缓存歌库路径 [绝对路径]\n" +
"注:默认歌库为网易云热歌榜\n" +
"1.可在后面添加“-动漫”进行动漫歌猜歌\n-这个只能猜歌名和歌手\n" +
"2.可在后面添加“-动漫2”进行动漫歌猜歌\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() + "setpath.txt"
if file.IsExist(cfgfile) {
b, err := os.ReadFile(cfgfile)
if err == nil {
musicPath = binary.BytesToString(b)
logrus.Infoln("[guessmusic] set dir to", musicPath)
}
}
engine.OnRegex(`^设置缓存歌库路径(.*)$`, 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) {
musicPath = ctx.State["regex_matched"].([]string)[1]
if musicPath == "" {
ctx.SendChain(message.Text("请输入正确的路径!"))
}
musicPath = strings.ReplaceAll(musicPath, "\\", "/")
if !strings.HasSuffix(musicPath, "/") {
musicPath += "/"
}
err := os.WriteFile(cfgfile, binary.StringToBytes(musicPath), 0644)
if err == nil {
ctx.SendChain(message.Text("成功!"))
} else {
ctx.SendChain(message.Text("ERROR:", err))
}
})
engine.OnRegex(`^(个人|团队)猜歌(-动漫|-动漫2)?$`, zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
mode := ctx.State["regex_matched"].([]string)[2]
gid := strconv.FormatInt(ctx.Event.GroupID, 10)
if mode == "-动漫2" {
ctx.SendChain(message.Text("正在准备歌曲,请稍等\n回答“-[歌曲名称|歌手|番剧|提示|取消]”\n一共3段语音6次机会"))
} else {
ctx.SendChain(message.Text("正在准备歌曲,请稍等\n回答“-[歌曲名称|歌手|提示|取消]”\n一共3段语音6次机会"))
}
// 随机抽歌
musicname, pathofmusic, err := musiclottery(mode, musicPath)
if err != nil {
ctx.SendChain(message.Text(err))
return
}
// 切割音频生成3个10秒的音频
outputPath := cachePath + gid + "/"
err = musiccut(musicname, pathofmusic, outputPath)
if err != nil {
ctx.SendChain(message.Text(err))
return
}
// 进行猜歌环节
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + "0.wav"))
answerstring := strings.Split(musicname, " - ")
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 countofmusic = 0 // 音频数量
var countofanswer = 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:
msg := make(message.Message, 0, 3)
msg = append(msg, message.Reply(ctx.Event.MessageID))
msg = append(msg, message.Text("猜歌超时,游戏结束\n答案是:",
"\n歌名:", answerstring[0],
"\n歌手:", answerstring[1]))
if mode == "-动漫2" {
msg = append(msg, message.Text("\n歌曲出自:", answerstring[2]))
}
ctx.Send(msg)
return
case <-wait.C:
wait.Reset(40 * time.Second)
countofmusic++
if countofmusic > 2 {
wait.Stop()
continue
}
ctx.SendChain(
message.Text("好像有些难度呢,再听这段音频,要仔细听哦"),
)
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + strconv.Itoa(countofmusic) + ".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()
msg := make(message.Message, 0, 3)
msg = append(msg, message.Reply(c.Event.MessageID))
msg = append(msg, message.Text("游戏已取消,猜歌答案是",
"\n歌名:", answerstring[0],
"\n歌手:", answerstring[1]))
if mode == "-动漫2" {
msg = append(msg, message.Text("\n歌曲出自:", answerstring[2]))
}
ctx.Send(msg)
return
}
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("你无权限取消"),
),
)
case answer == "提示":
countofmusic++
if countofmusic > 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(countofmusic) + ".wav"))
case strings.Contains(answerstring[0], answer) || strings.EqualFold(answerstring[0], answer):
wait.Stop()
tick.Stop()
after.Stop()
msg := make(message.Message, 0, 3)
msg = append(msg, message.Reply(c.Event.MessageID))
msg = append(msg, message.Text("太棒了,你猜对歌曲名了!答案是",
"\n歌名:", answerstring[0],
"\n歌手:", answerstring[1]))
if mode == "-动漫2" {
msg = append(msg, message.Text("\n歌曲出自:", answerstring[2]))
}
ctx.Send(msg)
return
case answerstring[1] == "未知" && answer == "未知":
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("该模式禁止回答“未知”"),
),
)
case strings.Contains(answerstring[1], answer) || strings.EqualFold(answerstring[1], answer):
wait.Stop()
tick.Stop()
after.Stop()
msg := make(message.Message, 0, 3)
msg = append(msg, message.Reply(c.Event.MessageID))
msg = append(msg, message.Text("太棒了,你猜对歌手名了!答案是",
"\n歌名:", answerstring[0],
"\n歌手:", answerstring[1]))
if mode == "-动漫2" {
msg = append(msg, message.Text("\n歌曲出自:", answerstring[2]))
}
ctx.Send(msg)
return
default:
if mode == "-动漫2" && (strings.Contains(answerstring[2], answer) || strings.EqualFold(answerstring[2], answer)) {
wait.Stop()
tick.Stop()
after.Stop()
ctx.Send(message.ReplyWithMessage(c.Event.MessageID,
message.Text("太棒了,你猜对番剧名了!答案是:",
"\n歌名:", answerstring[0],
"\n歌手:", answerstring[1],
"\n歌曲出自:", answerstring[2]),
))
return
}
countofmusic++
switch {
case countofmusic > 2 && countofanswer < 6:
wait.Stop()
countofanswer++
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("答案不对哦,加油啊~"),
),
)
case countofmusic > 2:
wait.Stop()
tick.Stop()
after.Stop()
msg := make(message.Message, 0, 3)
msg = append(msg, message.Reply(c.Event.MessageID))
msg = append(msg, message.Text("次数到了,你没能猜出来。\n答案是:",
"\n歌名:", answerstring[0],
"\n歌手:", answerstring[1]))
if mode == "-动漫2" {
msg = append(msg, message.Text("\n歌曲出自:", answerstring[2]))
}
ctx.Send(msg)
return
default:
wait.Reset(40 * time.Second)
countofanswer++
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("答案不对,再听这段音频,要仔细听哦"),
),
)
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + strconv.Itoa(countofmusic) + ".wav"))
}
}
}
}
})
}
// 随机抽取音乐
func musiclottery(mode, musicPath string) (musicname, pathofmusic string, err error) {
switch mode {
case "-动漫":
pathofmusic = musicPath + "动漫/"
case "-动漫2":
pathofmusic = musicPath + "动漫2/"
default:
pathofmusic = musicPath + "歌榜/"
}
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
}
// 随机抽取音乐从本地或者线上
switch {
case len(files) == 0:
// 如果没有任何本地就下载歌曲
switch mode {
case "-动漫":
musicname, err = getpaugramdata(pathofmusic)
case "-动漫2":
musicname, err = getanimedata(pathofmusic)
default:
musicname, err = getuomgdata(pathofmusic)
}
if err != nil {
err = errors.Errorf("[本地数据为0歌曲下载错误]ERROR:%s", err)
return
}
case rand.Intn(2) == 0:
// [0,1)只会取到0rand不允许的
if len(files) > 1 {
musicname = strings.Replace(files[rand.Intn(len(files))].Name(), ".mp3", "", 1)
} else {
musicname = strings.Replace(files[0].Name(), ".mp3", "", 1)
}
default:
switch mode {
case "-动漫":
musicname, err = getpaugramdata(pathofmusic)
case "-动漫2":
musicname, err = getanimedata(pathofmusic)
default:
musicname, err = getuomgdata(pathofmusic)
}
if err != nil {
// 如果下载失败就从本地抽一个歌曲
if len(files) > 1 {
musicname = strings.Replace(files[rand.Intn(len(files))].Name(), ".mp3", "", 1)
} else {
musicname = strings.Replace(files[0].Name(), ".mp3", "", 1)
}
err = nil
}
}
return
}
// 下载保罗API的歌曲
func getpaugramdata(musicPath string) (musicname string, err error) {
api := "https://api.paugram.com/acgm/?list=1"
referer := "https://api.paugram.com/"
data, err := web.RequestDataWith(web.NewDefaultClient(), api, "GET", referer, ua)
if err != nil {
return
}
name := gjson.Get(binary.BytesToString(data), "title").String()
artistsname := gjson.Get(binary.BytesToString(data), "artist").String()
musicurl := gjson.Get(binary.BytesToString(data), "link").String()
if name == "" || artistsname == "" {
err = errors.Errorf("the music is missed")
return
}
musicname = name + " - " + artistsname
downmusic := musicPath + "/" + musicname + ".mp3"
response, err := http.Head(musicurl)
if err != nil || response.StatusCode != 200 {
err = errors.Errorf("the music is missed")
return
}
if file.IsNotExist(downmusic) {
data, err = web.GetData(musicurl + ".mp3")
if err != nil {
return
}
err = os.WriteFile(downmusic, data, 0666)
if err != nil {
return
}
}
return
}
// 下载animeMusic API的歌曲
func getanimedata(musicPath string) (musicname string, err error) {
api := "https://anime-music.jijidown.com/api/v2/music"
referer := "https://anime-music.jijidown.com/"
data, err := web.RequestDataWith(web.NewDefaultClient(), api, "GET", referer, ua)
if err != nil {
return
}
name := gjson.Get(binary.BytesToString(data), "res").Get("title").String()
artistsname := gjson.Get(binary.BytesToString(data), "res").Get("author").String()
acgname := gjson.Get(binary.BytesToString(data), "res").Get("anime_info").Get("title").String()
musicurl := gjson.Get(binary.BytesToString(data), "res").Get("play_url").String()
if name == "" || artistsname == "" {
err = errors.Errorf("the music is missed")
return
}
musicname = name + " - " + artistsname + " - " + acgname
downmusic := musicPath + "/" + musicname + ".mp3"
response, err := http.Head(musicurl)
if err != nil || response.StatusCode != 200 {
err = errors.Errorf("the music is missed")
return
}
if file.IsNotExist(downmusic) {
data, err = web.GetData(musicurl + ".mp3")
if err != nil {
return
}
err = os.WriteFile(downmusic, data, 0666)
if err != nil {
return
}
}
return
}
// 下载网易云热歌榜音乐
func getuomgdata(musicPath string) (musicname string, err error) {
api := "https://api.uomg.com/api/rand.music?sort=%E7%83%AD%E6%AD%8C%E6%A6%9C&format=json"
referer := "https://api.uomg.com/api/rand.music"
data, err := web.RequestDataWith(web.NewDefaultClient(), api, "GET", referer, ua)
if err != nil {
return
}
musicdata := gjson.Get(binary.BytesToString(data), "data")
name := musicdata.Get("name").String()
musicurl := musicdata.Get("url").String()
artistsname := musicdata.Get("artistsname").String()
musicname = name + " - " + artistsname
downmusic := musicPath + "/" + musicname + ".mp3"
if file.IsNotExist(downmusic) {
data, err = web.GetData(musicurl + ".mp3")
if err != nil {
return
}
err = os.WriteFile(downmusic, data, 0666)
if err != nil {
return
}
}
return
}
// 切割音乐成三个10s音频
func musiccut(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 + ".mp3",
"-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
}