mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-18 20:50:12 +08:00
474 lines
14 KiB
Go
474 lines
14 KiB
Go
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("正在校验歌单信息,请稍等"))
|
||
// 是否存在该歌单
|
||
if file.IsNotExist(cfg.MusicPath + listName) {
|
||
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
|
||
}
|
||
}
|
||
ctx.SendChain(message.Text("开始下载歌曲,需要一定时间下载,请稍等"))
|
||
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
|
||
}
|