fix gui freeze bug (work round)

This commit is contained in:
Aynakeya
2023-02-19 04:23:03 -08:00
parent 9d99a74faf
commit 6f2349e17b
106 changed files with 2051 additions and 1580 deletions

View File

@@ -0,0 +1,139 @@
package provider
import (
"AynaLivePlayer/core/adapter"
"AynaLivePlayer/core/model"
"fmt"
"github.com/tidwall/gjson"
"net/url"
"regexp"
)
type Bilibili struct {
InfoApi string
FileApi string
SearchApi string
IdRegex0 *regexp.Regexp
IdRegex1 *regexp.Regexp
}
func NewBilibili(config adapter.MediaProviderConfig) adapter.MediaProvider {
return &Bilibili{
InfoApi: "https://www.bilibili.com/audio/music-service-c/web/song/info?sid=%s",
FileApi: "https://api.bilibili.com/audio/music-service-c/url?device=phone&mid=8047632&mobi_app=iphone&platform=ios&privilege=2&songid=%s&quality=2",
SearchApi: "https://api.bilibili.com/audio/music-service-c/s?search_type=music&keyword=%s&page=1&pagesize=100",
IdRegex0: regexp.MustCompile("^[0-9]+"),
IdRegex1: regexp.MustCompile("^au[0-9]+"),
}
}
func _newBilibili() *Bilibili {
return &Bilibili{
InfoApi: "https://www.bilibili.com/audio/music-service-c/web/song/info?sid=%s",
FileApi: "https://api.bilibili.com/audio/music-service-c/url?device=phone&mid=8047632&mobi_app=iphone&platform=ios&privilege=2&songid=%s&quality=2",
SearchApi: "https://api.bilibili.com/audio/music-service-c/s?search_type=music&keyword=%s&page=1&pagesize=100",
IdRegex0: regexp.MustCompile("^[0-9]+"),
IdRegex1: regexp.MustCompile("^au[0-9]+"),
}
}
var BilibiliAPI *Bilibili
func init() {
BilibiliAPI = _newBilibili()
Providers[BilibiliAPI.GetName()] = BilibiliAPI
}
func (b *Bilibili) GetName() string {
return "bilibili"
}
func (b *Bilibili) MatchMedia(keyword string) *model.Media {
if id := b.IdRegex0.FindString(keyword); id != "" {
return &model.Media{
Meta: model.Meta{
Name: b.GetName(),
Id: id,
},
}
}
if id := b.IdRegex1.FindString(keyword); id != "" {
return &model.Media{
Meta: model.Meta{
Name: b.GetName(),
Id: id[2:],
},
}
}
return nil
}
func (b *Bilibili) FormatPlaylistUrl(uri string) string {
return ""
}
func (b *Bilibili) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
return nil, ErrorExternalApi
}
func (b *Bilibili) Search(keyword string) ([]*model.Media, error) {
resp := httpGetString(fmt.Sprintf(b.SearchApi, url.QueryEscape(keyword)), map[string]string{
"user-agent": "BiliMusic/2.233.3",
})
if resp == "" {
return nil, ErrorExternalApi
}
result := make([]*model.Media, 0)
gjson.Get(resp, "data.result").ForEach(func(key, value gjson.Result) bool {
result = append(result, &model.Media{
Title: value.Get("title").String(),
Cover: model.Picture{Url: value.Get("cover").String()},
Artist: value.Get("author").String(),
Meta: model.Meta{
Name: b.GetName(),
Id: value.Get("id").String(),
},
})
return true
})
return result, nil
}
func (b *Bilibili) UpdateMedia(media *model.Media) error {
resp := httpGetString(fmt.Sprintf(b.InfoApi, media.Meta.(model.Meta).Id), map[string]string{
"user-agent": "BiliMusic/2.233.3",
})
if resp == "" {
return ErrorExternalApi
}
if gjson.Get(resp, "data.title").String() == "" {
return ErrorExternalApi
}
media.Title = gjson.Get(resp, "data.title").String()
media.Cover.Url = gjson.Get(resp, "data.cover").String()
media.Artist = gjson.Get(resp, "data.author").String()
media.Album = media.Title
return nil
}
func (b *Bilibili) UpdateMediaUrl(media *model.Media) error {
resp := httpGetString(fmt.Sprintf(b.FileApi, media.Meta.(model.Meta).Id), map[string]string{
"user-agent": "BiliMusic/2.233.3",
})
if resp == "" {
return ErrorExternalApi
}
media.Header = map[string]string{
"user-agent": "BiliMusic/2.233.3",
}
uri := gjson.Get(resp, "data.cdns.0").String()
if uri == "" {
return ErrorExternalApi
}
media.Url = uri
return nil
}
func (k *Bilibili) UpdateMediaLyric(media *model.Media) error {
return nil
}

View File

@@ -0,0 +1,60 @@
package provider
import (
"AynaLivePlayer/core/adapter"
"AynaLivePlayer/core/model"
"fmt"
"testing"
)
func TestBilibili_Search(t *testing.T) {
var api adapter.MediaProvider = BilibiliAPI
result, err := api.Search("染 reol")
if err != nil {
fmt.Println(1, err)
return
}
fmt.Println(result)
media := result[0]
fmt.Println(*media)
err = api.UpdateMediaUrl(media)
fmt.Println(err)
fmt.Println(media.Url)
}
func TestBilibili_GetMusicMeta(t *testing.T) {
var api adapter.MediaProvider = BilibiliAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "1560601",
},
}
err := api.UpdateMedia(&media)
fmt.Println(err)
if err != nil {
return
}
fmt.Println(media)
}
func TestBilibili_GetMusic(t *testing.T) {
var api adapter.MediaProvider = BilibiliAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "1560601",
},
}
err := api.UpdateMedia(&media)
if err != nil {
return
}
err = api.UpdateMediaUrl(&media)
if err != nil {
return
}
fmt.Println(media)
fmt.Println(media.Url)
}

View File

@@ -0,0 +1,174 @@
package provider
import (
"AynaLivePlayer/common/util"
"AynaLivePlayer/core/adapter"
"AynaLivePlayer/core/model"
"fmt"
"github.com/jinzhu/copier"
"github.com/tidwall/gjson"
"net/url"
"regexp"
)
type BilibiliVideo struct {
InfoApi string
FileApi string
SearchApi string
BVRegex *regexp.Regexp
IdRegex *regexp.Regexp
PageRegex *regexp.Regexp
header map[string]string
}
func NewBilibiliVideo(config adapter.MediaProviderConfig) adapter.MediaProvider {
return &BilibiliVideo{
InfoApi: "https://api.bilibili.com/x/web-interface/view/detail?bvid=%s&aid=&jsonp=jsonp",
FileApi: "https://api.bilibili.com/x/player/playurl?type=&otype=json&fourk=1&qn=32&avid=&bvid=%s&cid=%s",
SearchApi: "https://api.bilibili.com/x/web-interface/search/type?search_type=video&page=1&keyword=%s",
BVRegex: regexp.MustCompile("^BV[0-9A-Za-z]+"),
IdRegex: regexp.MustCompile("^BV[0-9A-Za-z]+(\\?p=[0-9]+)?"),
PageRegex: regexp.MustCompile("p=[0-9]+"),
header: map[string]string{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
"Referer": "https://www.bilibili.com/",
"Origin": "https://www.bilibili.com",
},
}
}
func _newBilibiliVideo() *BilibiliVideo {
return &BilibiliVideo{
InfoApi: "https://api.bilibili.com/x/web-interface/view/detail?bvid=%s&aid=&jsonp=jsonp",
FileApi: "https://api.bilibili.com/x/player/playurl?type=&otype=json&fourk=1&qn=32&avid=&bvid=%s&cid=%s",
SearchApi: "https://api.bilibili.com/x/web-interface/search/type?search_type=video&page=1&keyword=%s",
BVRegex: regexp.MustCompile("^BV[0-9A-Za-z]+"),
IdRegex: regexp.MustCompile("^BV[0-9A-Za-z]+(\\?p=[0-9]+)?"),
PageRegex: regexp.MustCompile("p=[0-9]+"),
header: map[string]string{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0",
"Referer": "https://www.bilibili.com/",
"Origin": "https://www.bilibili.com",
"Cookie": "buvid3=9A8B3564-BDA9-407F-B45F-D5C40786CA49167618infoc;",
},
}
}
var BilibiliVideoAPI *BilibiliVideo
func init() {
BilibiliVideoAPI = _newBilibiliVideo()
Providers[BilibiliVideoAPI.GetName()] = BilibiliVideoAPI
}
func (b *BilibiliVideo) getPage(bv string) int {
if page := b.PageRegex.FindString(bv); page != "" {
return util.Atoi(page[2:])
}
return 0
}
func (b *BilibiliVideo) getBv(bv string) string {
return b.BVRegex.FindString(bv)
}
func (b *BilibiliVideo) GetName() string {
return "bilibili-video"
}
func (b *BilibiliVideo) MatchMedia(keyword string) *model.Media {
if id := b.IdRegex.FindString(keyword); id != "" {
return &model.Media{
Meta: model.Meta{
Name: b.GetName(),
Id: id,
},
}
}
return nil
}
func (b *BilibiliVideo) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
return nil, ErrorExternalApi
}
func (b *BilibiliVideo) FormatPlaylistUrl(uri string) string {
return ""
}
func (b *BilibiliVideo) Search(keyword string) ([]*model.Media, error) {
resp := httpGetString(fmt.Sprintf(b.SearchApi, url.QueryEscape(keyword)), b.header)
if resp == "" {
return nil, ErrorExternalApi
}
jresp := gjson.Parse(resp)
if jresp.Get("code").String() != "0" {
return nil, ErrorExternalApi
}
result := make([]*model.Media, 0)
r := regexp.MustCompile("</?em[^>]*>")
jresp.Get("data.result").ForEach(func(key, value gjson.Result) bool {
result = append(result, &model.Media{
Title: r.ReplaceAllString(value.Get("title").String(), ""),
Cover: model.Picture{Url: "https:" + value.Get("pic").String()},
Artist: value.Get("author").String(),
Meta: model.Meta{
Name: b.GetName(),
Id: value.Get("bvid").String(),
},
})
return true
})
return result, nil
}
func (b *BilibiliVideo) UpdateMedia(media *model.Media) error {
resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(model.Meta).Id)), nil)
if resp == "" {
return ErrorExternalApi
}
jresp := gjson.Parse(resp)
if jresp.Get("data.View.title").String() == "" {
return ErrorExternalApi
}
media.Title = jresp.Get("data.View.title").String()
media.Artist = jresp.Get("data.View.owner.name").String()
media.Cover.Url = jresp.Get("data.View.pic").String()
media.Album = media.Title
return nil
}
func (b *BilibiliVideo) UpdateMediaUrl(media *model.Media) error {
resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(model.Meta).Id)), nil)
if resp == "" {
return ErrorExternalApi
}
jresp := gjson.Parse(resp)
page := b.getPage(media.Meta.(model.Meta).Id) - 1
cid := jresp.Get(fmt.Sprintf("data.View.pages.%d.cid", page)).String()
if cid == "" {
cid = jresp.Get("data.View.cid").String()
}
if cid == "" {
return ErrorExternalApi
}
resp = httpGetString(fmt.Sprintf(b.FileApi, b.getBv(media.Meta.(model.Meta).Id), cid), b.header)
if resp == "" {
return ErrorExternalApi
}
jresp = gjson.Parse(resp)
uri := jresp.Get("data.durl.0.url").String()
if uri == "" {
return ErrorExternalApi
}
media.Url = uri
header := make(map[string]string)
_ = copier.Copy(&header, &b.header)
header["Referer"] = fmt.Sprintf("https://www.bilibili.com/video/%s", b.getBv(media.Meta.(model.Meta).Id))
media.Header = b.header
return nil
}
func (b *BilibiliVideo) UpdateMediaLyric(media *model.Media) error {
return nil
}

View File

@@ -0,0 +1,100 @@
package provider
import (
"AynaLivePlayer/core/adapter"
"AynaLivePlayer/core/model"
"fmt"
"regexp"
"testing"
)
func TestBV_GetMusicMeta(t *testing.T) {
var api adapter.MediaProvider = BilibiliVideoAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "BV1434y1q71P",
},
}
err := api.UpdateMedia(&media)
fmt.Println(err)
if err != nil {
return
}
fmt.Println(media)
}
func TestBV_GetMusic(t *testing.T) {
var api adapter.MediaProvider = BilibiliVideoAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "BV1434y1q71P",
},
}
err := api.UpdateMedia(&media)
if err != nil {
return
}
err = api.UpdateMediaUrl(&media)
if err != nil {
return
}
//fmt.Println(media)
fmt.Println(media.Url)
}
func TestBV_Regex(t *testing.T) {
fmt.Println(regexp.MustCompile("^BV[0-9A-Za-z]+(\\?p=[0-9]+)?").FindString("BV1gA411P7ir?p=3"))
}
func TestBV_GetMusicMeta2(t *testing.T) {
var api adapter.MediaProvider = BilibiliVideoAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "BV1gA411P7ir?p=3",
},
}
err := api.UpdateMedia(&media)
fmt.Println(err)
if err != nil {
return
}
fmt.Println(media)
}
func TestBV_GetMusic2(t *testing.T) {
var api adapter.MediaProvider = BilibiliVideoAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "BV1gA411P7ir?p=3",
},
}
err := api.UpdateMedia(&media)
if err != nil {
return
}
err = api.UpdateMediaUrl(&media)
if err != nil {
return
}
//fmt.Println(media)
fmt.Println(media.Url)
}
func TestBV_Search(t *testing.T) {
var api adapter.MediaProvider = BilibiliVideoAPI
result, err := api.Search("家有女友")
if err != nil {
fmt.Println(1, err)
return
}
fmt.Println(len(result))
for _, r := range result {
fmt.Println(r.Artist)
}
}

View File

@@ -0,0 +1,8 @@
package provider
import "errors"
var (
ErrorExternalApi = errors.New("external api error")
ErrorNoSuchProvider = errors.New("not such provider")
)

29
adapters/provider/http.go Normal file
View File

@@ -0,0 +1,29 @@
package provider
import (
"github.com/go-resty/resty/v2"
"time"
)
func httpGet(url string, header map[string]string) (*resty.Response, error) {
resp, err := resty.New().
SetTimeout(time.Second * 3).R().
SetHeaders(header).
Get(url)
return resp, err
}
func httpGetString(url string, header map[string]string) string {
resp, err := httpGet(url, header)
if err != nil {
return ""
}
return resp.String()
}
func httpHead(url string, header map[string]string) (*resty.Response, error) {
resp, err := resty.New().
SetTimeout(time.Second * 3).R().
SetHeaders(header).
Head(url)
return resp, err
}

230
adapters/provider/kuwo.go Normal file
View File

@@ -0,0 +1,230 @@
package provider
import (
"AynaLivePlayer/core/adapter"
"AynaLivePlayer/core/model"
"fmt"
"github.com/tidwall/gjson"
"html"
"net/url"
"regexp"
"strings"
)
type Kuwo struct {
InfoApi string
FileApi string
SearchCookie string
SearchApi string
LyricApi string
PlaylistApi string
PlaylistRegex0 *regexp.Regexp
PlaylistRegex1 *regexp.Regexp
IdRegex0 *regexp.Regexp
IdRegex1 *regexp.Regexp
}
func NewKuwo(config adapter.MediaProviderConfig) adapter.MediaProvider {
return &Kuwo{
InfoApi: "http://www.kuwo.cn/api/www/music/musicInfo?mid=%s&httpsStatus=1",
//FileApi: "http://www.kuwo.cn/api/v1/www/music/playUrl?mid=%d&type=music&httpsStatus=1",
FileApi: "http://antiserver.kuwo.cn/anti.s?type=convert_url&format=mp3&response=url&rid=MUSIC_%s",
SearchCookie: "http://kuwo.cn/search/list?key=%s",
SearchApi: "http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key=%s&pn=%d&rn=%d",
LyricApi: "http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=%s",
PlaylistApi: "http://www.kuwo.cn/api/www/playlist/playListInfo?pid=%s&pn=%d&rn=%d&httpsStatus=1",
PlaylistRegex0: regexp.MustCompile("[0-9]+"),
PlaylistRegex1: regexp.MustCompile("playlist/[0-9]+"),
IdRegex0: regexp.MustCompile("^[0-9]+"),
IdRegex1: regexp.MustCompile("^kw[0-9]+"),
}
}
func _newKuwo() *Kuwo {
return &Kuwo{
InfoApi: "http://www.kuwo.cn/api/www/music/musicInfo?mid=%s&httpsStatus=1",
//FileApi: "http://www.kuwo.cn/api/v1/www/music/playUrl?mid=%d&type=music&httpsStatus=1",
FileApi: "http://antiserver.kuwo.cn/anti.s?type=convert_url&format=mp3&response=url&rid=MUSIC_%s",
SearchCookie: "http://kuwo.cn/search/list?key=%s",
SearchApi: "http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key=%s&pn=%d&rn=%d",
LyricApi: "http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=%s",
PlaylistApi: "http://www.kuwo.cn/api/www/playlist/playListInfo?pid=%s&pn=%d&rn=%d&httpsStatus=1",
PlaylistRegex0: regexp.MustCompile("[0-9]+"),
PlaylistRegex1: regexp.MustCompile("playlist/[0-9]+"),
IdRegex0: regexp.MustCompile("^[0-9]+"),
IdRegex1: regexp.MustCompile("^kw[0-9]+"),
}
}
var KuwoAPI *Kuwo
func init() {
KuwoAPI = _newKuwo()
Providers[KuwoAPI.GetName()] = KuwoAPI
}
func (k *Kuwo) GetName() string {
return "kuwo"
}
func (k *Kuwo) MatchMedia(keyword string) *model.Media {
if id := k.IdRegex0.FindString(keyword); id != "" {
return &model.Media{
Meta: model.Meta{
Name: k.GetName(),
Id: id,
},
}
}
if id := k.IdRegex1.FindString(keyword); id != "" {
return &model.Media{
Meta: model.Meta{
Name: k.GetName(),
Id: id[2:],
},
}
}
return nil
}
func (k *Kuwo) FormatPlaylistUrl(uri string) string {
var id string
id = k.PlaylistRegex0.FindString(uri)
if id != "" {
return id
}
id = k.PlaylistRegex1.FindString(uri)
if id != "" {
return id[9:]
}
return ""
}
//func (k *Kuwo) _kuwoGet(url string) string {
// searchCookie, err := httpHead(fmt.Sprintf(k.SearchCookie, "any"), nil)
// if err != nil {
// return ""
// }
// kwToken, ok := util.SliceString(regexp.MustCompile("kw_token=([^;])*;").FindString(searchCookie.Header().Get("set-cookie")), 9, -1)
// if !ok {
// return ""
// }
// return httpGetString(url, map[string]string{
// "cookie": "kw_token=" + kwToken,
// "csrf": kwToken,
// "referer": "http://www.kuwo.cn/",
// })
//}
func (k *Kuwo) _kuwoGet(url string) string {
return httpGetString(url, map[string]string{
"cookie": "kw_token=" + "95MWTYC4FP",
"csrf": "95MWTYC4FP",
"referer": "http://www.kuwo.cn/",
})
}
func (k *Kuwo) Search(keyword string) ([]*model.Media, error) {
resp := k._kuwoGet(fmt.Sprintf(k.SearchApi, url.QueryEscape(keyword), 1, 64))
if resp == "" {
return nil, ErrorExternalApi
}
result := make([]*model.Media, 0)
gjson.Parse(resp).Get("data.list").ForEach(func(key, value gjson.Result) bool {
result = append(result, &model.Media{
Title: html.UnescapeString(value.Get("name").String()),
Cover: model.Picture{Url: value.Get("pic").String()},
Artist: value.Get("artist").String(),
Album: value.Get("album").String(),
Meta: model.Meta{
Name: k.GetName(),
Id: value.Get("rid").String(),
},
})
return true
})
return result, nil
}
func (k *Kuwo) UpdateMedia(media *model.Media) error {
resp := k._kuwoGet(fmt.Sprintf(k.InfoApi, media.Meta.(model.Meta).Id))
if resp == "" {
return ErrorExternalApi
}
jresp := gjson.Parse(resp)
if jresp.Get("data.musicrid").String() == "" {
return ErrorExternalApi
}
media.Title = html.UnescapeString(jresp.Get("data.name").String())
media.Cover.Url = jresp.Get("data.pic").String()
media.Artist = jresp.Get("data.artist").String()
media.Album = jresp.Get("data.album").String()
return nil
}
func (k *Kuwo) UpdateMediaUrl(media *model.Media) error {
result := httpGetString(fmt.Sprintf(k.FileApi, media.Meta.(model.Meta).Id), nil)
if result == "" {
return ErrorExternalApi
}
media.Url = result
return nil
}
func (k *Kuwo) UpdateMediaLyric(media *model.Media) error {
result := httpGetString(fmt.Sprintf(k.LyricApi, media.Meta.(model.Meta).Id), nil)
if result == "" {
return ErrorExternalApi
}
lrcs := make([]string, 0)
gjson.Parse(result).Get("data.lrclist").ForEach(func(key, value gjson.Result) bool {
lrcs = append(lrcs, fmt.Sprintf("[00:%s]%s", value.Get("time").String(), value.Get("lineLyric").String()))
return true
})
media.Lyric = strings.Join(lrcs, "\n")
return nil
}
func (k *Kuwo) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
medias := make([]*model.Media, 0)
var resp string
var jresp gjson.Result
for i := 1; i <= 20; i++ {
resp = k._kuwoGet(fmt.Sprintf(k.PlaylistApi, playlist.Id, i, 128))
if resp == "" {
break
}
//fmt.Println(resp[:100])
jresp = gjson.Parse(resp)
//fmt.Println(jresp.Get("code").String())
if jresp.Get("code").String() != "200" {
break
}
cnt := int(jresp.Get("data.total").Int())
//fmt.Println(cnt)
//fmt.Println(len(jresp.Get("data.musicList").Array()))
jresp.Get("data.musicList").ForEach(func(key, value gjson.Result) bool {
medias = append(
medias,
&model.Media{
Title: html.UnescapeString(value.Get("name").String()),
Artist: value.Get("artist").String(),
Cover: model.Picture{Url: value.Get("pic").String()},
Album: value.Get("album").String(),
Meta: model.Meta{
Name: k.GetName(),
Id: value.Get("rid").String(),
},
})
return true
})
if cnt <= i*100 {
break
}
}
if len(medias) == 0 {
return nil, ErrorExternalApi
}
return medias, nil
}

View File

@@ -0,0 +1,89 @@
package provider
import (
"AynaLivePlayer/core/adapter"
"AynaLivePlayer/core/model"
"fmt"
"testing"
)
func TestKuwo_Search(t *testing.T) {
var api adapter.MediaProvider = KuwoAPI
result, err := api.Search("染 reol")
if err != nil {
return
}
fmt.Println(result)
media := result[0]
err = api.UpdateMediaUrl(media)
fmt.Println(err)
fmt.Println(media.Url)
}
func TestKuwo_GetMusicMeta(t *testing.T) {
var api adapter.MediaProvider = KuwoAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "22804772",
},
}
err := api.UpdateMedia(&media)
fmt.Println(err)
if err != nil {
return
}
fmt.Println(media)
}
func TestKuwo_GetMusic(t *testing.T) {
var api adapter.MediaProvider = KuwoAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "22804772",
},
}
err := api.UpdateMedia(&media)
if err != nil {
return
}
err = api.UpdateMediaUrl(&media)
if err != nil {
return
}
fmt.Println(media)
fmt.Println(media.Url)
}
func TestKuwo_UpdateMediaLyric(t *testing.T) {
var api adapter.MediaProvider = KuwoAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "22804772",
},
}
err := api.UpdateMediaLyric(&media)
fmt.Println(err)
fmt.Println(media.Lyric)
}
func TestKuwo_GetPlaylist(t *testing.T) {
var api adapter.MediaProvider = KuwoAPI
playlist, err := api.GetPlaylist(&model.Meta{
Name: api.GetName(),
//Id: "1082685104",
Id: "2959147566",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(playlist))
for _, media := range playlist {
fmt.Println(media.Title, media.Artist, media.Album)
}
}

165
adapters/provider/local.go Normal file
View File

@@ -0,0 +1,165 @@
package provider
import (
"AynaLivePlayer/common/util"
"AynaLivePlayer/core/adapter"
"AynaLivePlayer/core/model"
"os"
"sort"
"strings"
)
type _LocalPlaylist struct {
Name string
Medias []*model.Media
}
type Local struct {
localDir string
Playlists []*_LocalPlaylist
}
func NewLocalCtor(config adapter.MediaProviderConfig) adapter.MediaProvider {
localDir, ok := config["local_dir"]
if !ok {
localDir = "./local"
}
l := &Local{Playlists: make([]*_LocalPlaylist, 0), localDir: localDir}
if err := os.MkdirAll(localDir, 0755); err != nil {
return l
}
for _, n := range getPlaylistNames(localDir) {
l.Playlists = append(l.Playlists, &_LocalPlaylist{Name: n})
}
for i, _ := range l.Playlists {
_ = readLocalPlaylist(localDir, l.Playlists[i])
}
LocalAPI = l
Providers[LocalAPI.GetName()] = LocalAPI
return l
}
var LocalAPI *Local
func NewLocal(localdir string) *Local {
l := &Local{Playlists: make([]*_LocalPlaylist, 0), localDir: localdir}
if err := os.MkdirAll(localdir, 0755); err != nil {
return l
}
for _, n := range getPlaylistNames(localdir) {
l.Playlists = append(l.Playlists, &_LocalPlaylist{Name: n})
}
for i, _ := range l.Playlists {
_ = readLocalPlaylist(localdir, l.Playlists[i])
}
LocalAPI = l
Providers[LocalAPI.GetName()] = LocalAPI
return l
}
func (l *Local) GetName() string {
return "local"
}
func (l *Local) MatchMedia(keyword string) *model.Media {
return nil
}
func (l *Local) UpdateMediaLyric(media *model.Media) error {
// already update in UpdateMedia, do nothing
return nil
}
func (l *Local) FormatPlaylistUrl(uri string) string {
return uri
}
func (l *Local) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
var pl *_LocalPlaylist = nil
for _, p := range l.Playlists {
if p.Name == playlist.Id {
pl = p
}
}
if pl == nil {
l.Playlists = append(l.Playlists, &_LocalPlaylist{Name: playlist.Id})
pl = l.Playlists[len(l.Playlists)-1]
}
if readLocalPlaylist(l.localDir, pl) != nil {
return nil, ErrorExternalApi
}
return pl.Medias, nil
}
func (l *Local) Search(keyword string) ([]*model.Media, error) {
allMedias := make([]*model.Media, 0)
for _, p := range l.Playlists {
for _, m := range p.Medias {
allMedias = append(allMedias, m)
}
}
MediaSort(keyword, allMedias)
c := util.Min(len(allMedias), 32)
medias := make([]*model.Media, c)
for i := 0; i < c; i++ {
medias[i] = allMedias[i].Copy()
}
return medias, nil
}
func (l *Local) SearchV1(keyword string) ([]*model.Media, error) {
result := make([]struct {
M *model.Media
N int
}, 0)
keywords := strings.Split(keyword, " ")
for _, p := range l.Playlists {
for _, m := range p.Medias {
title := strings.ToLower(m.Title)
artist := strings.ToLower(m.Artist)
n := 0
for _, k := range keywords {
kw := strings.ToLower(k)
if strings.Contains(title, kw) || strings.Contains(artist, kw) {
n++
}
if kw == title {
n += 3
}
}
if n > 0 {
result = append(result, struct {
M *model.Media
N int
}{M: m, N: n})
}
}
}
sort.Slice(result, func(i, j int) bool {
return result[i].N > result[j].N
})
medias := make([]*model.Media, len(result))
for i, r := range result {
medias[i] = r.M.Copy()
}
return medias, nil
}
func (l *Local) UpdateMedia(media *model.Media) error {
mediaPath := media.Meta.(model.Meta).Id
_, err := os.Stat(mediaPath)
if err != nil {
return err
}
return readMediaFile(media)
}
func (l *Local) UpdateMediaUrl(media *model.Media) error {
mediaPath := media.Meta.(model.Meta).Id
_, err := os.Stat(mediaPath)
if err != nil {
return err
}
media.Url = mediaPath
return nil
}

View File

@@ -0,0 +1,72 @@
package provider
import (
"AynaLivePlayer/common/util"
"AynaLivePlayer/core/model"
"github.com/dhowden/tag"
"io/ioutil"
"os"
"path/filepath"
)
func getPlaylistNames(localdir string) []string {
names := make([]string, 0)
items, _ := ioutil.ReadDir(localdir)
for _, item := range items {
if item.IsDir() {
names = append(names, item.Name())
}
}
return names
}
// readLocalPlaylist read files under a directory
// and return a _LocalPlaylist object.
// This function assume this directory exists
func readLocalPlaylist(localdir string, playlist *_LocalPlaylist) error {
p1th := playlist.Name
playlist.Medias = make([]*model.Media, 0)
fullPath := filepath.Join(localdir, p1th)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
return err
}
items, _ := ioutil.ReadDir(fullPath)
for _, item := range items {
// if item is a file, read file
if !item.IsDir() {
fn := item.Name()
media := model.Media{
Meta: model.Meta{
Name: LocalAPI.GetName(),
Id: filepath.Join(fullPath, fn),
},
}
if readMediaFile(&media) != nil {
continue
}
playlist.Medias = append(playlist.Medias, &media)
}
}
return nil
}
func readMediaFile(media *model.Media) error {
p := media.Meta.(model.Meta).Id
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close()
meta, err := tag.ReadFrom(f)
if err != nil {
return err
}
media.Title = util.GetOrDefault(meta.Title(), filepath.Base(p))
media.Artist = util.GetOrDefault(meta.Artist(), "Unknown")
media.Album = util.GetOrDefault(meta.Album(), "Unknown")
media.Lyric = meta.Lyrics()
if meta.Picture() != nil {
media.Cover.Data = meta.Picture().Data
}
return nil
}

View File

@@ -0,0 +1,25 @@
package provider
import (
"fmt"
"io/ioutil"
"testing"
)
func TestLocal_Read(t *testing.T) {
items, _ := ioutil.ReadDir(".")
for _, item := range items {
if item.IsDir() {
subitems, _ := ioutil.ReadDir(item.Name())
for _, subitem := range subitems {
if !subitem.IsDir() {
// handle file there
fmt.Println(item.Name() + "/" + subitem.Name())
}
}
} else {
// handle file there
fmt.Println(item.Name())
}
}
}

View File

@@ -0,0 +1,239 @@
package provider
import (
"AynaLivePlayer/common/util"
"AynaLivePlayer/core/adapter"
"AynaLivePlayer/core/model"
neteaseApi "github.com/XiaoMengXinX/Music163Api-Go/api"
neteaseTypes "github.com/XiaoMengXinX/Music163Api-Go/types"
neteaseUtil "github.com/XiaoMengXinX/Music163Api-Go/utils"
"regexp"
"strconv"
"strings"
)
type Netease struct {
PlaylistRegex0 *regexp.Regexp
PlaylistRegex1 *regexp.Regexp
ReqData neteaseUtil.RequestData
IdRegex0 *regexp.Regexp
IdRegex1 *regexp.Regexp
loginStatus neteaseTypes.LoginStatusData
}
func NewNetease(config adapter.MediaProviderConfig) adapter.MediaProvider {
return &Netease{
PlaylistRegex0: regexp.MustCompile("^[0-9]+$"),
// https://music.163.com/playlist?id=2382819181&userid=95906480
PlaylistRegex1: regexp.MustCompile("playlist\\?id=[0-9]+"),
ReqData: neteaseUtil.RequestData{
Headers: neteaseUtil.Headers{
{
"X-Real-IP",
"118.88.88.88",
},
},
},
IdRegex0: regexp.MustCompile("^[0-9]+"),
IdRegex1: regexp.MustCompile("^wy[0-9]+"),
}
}
func _newNetease() *Netease {
return &Netease{
PlaylistRegex0: regexp.MustCompile("^[0-9]+$"),
// https://music.163.com/playlist?id=2382819181&userid=95906480
PlaylistRegex1: regexp.MustCompile("playlist\\?id=[0-9]+"),
ReqData: neteaseUtil.RequestData{
Headers: neteaseUtil.Headers{
{
"X-Real-IP",
"118.88.88.88",
},
},
},
IdRegex0: regexp.MustCompile("^[0-9]+"),
IdRegex1: regexp.MustCompile("^wy[0-9]+"),
}
}
var NeteaseAPI *Netease
func init() {
NeteaseAPI = _newNetease()
Providers[NeteaseAPI.GetName()] = NeteaseAPI
}
// Netease private helper method
func _neteaseGetArtistNames(data neteaseTypes.SongDetailData) string {
artists := make([]string, 0)
for _, a := range data.Ar {
artists = append(artists, a.Name)
}
return strings.Join(artists, ",")
}
// MediaProvider implementation
func (n *Netease) GetName() string {
return "netease"
}
func (n *Netease) MatchMedia(keyword string) *model.Media {
if id := n.IdRegex0.FindString(keyword); id != "" {
return &model.Media{
Meta: model.Meta{
Name: n.GetName(),
Id: id,
},
}
}
if id := n.IdRegex1.FindString(keyword); id != "" {
return &model.Media{
Meta: model.Meta{
Name: n.GetName(),
Id: id[2:],
},
}
}
return nil
}
func (n *Netease) FormatPlaylistUrl(uri string) string {
var id string
id = n.PlaylistRegex0.FindString(uri)
if id != "" {
return id
}
id = n.PlaylistRegex1.FindString(uri)
if id != "" {
return id[12:]
}
return ""
}
func (n *Netease) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
result, err := neteaseApi.GetPlaylistDetail(
n.ReqData, util.Atoi(playlist.Id))
if err != nil || result.Code != 200 {
return nil, ErrorExternalApi
}
cnt := len(result.Playlist.TrackIds)
if cnt == 0 {
return nil, ErrorExternalApi
}
ids := make([]int, len(result.Playlist.TrackIds))
for i := 0; i < cnt; i++ {
ids[i] = result.Playlist.TrackIds[i].Id
}
medias := make([]*model.Media, 0, cnt)
for index := 0; index < len(ids); index += 1000 {
result2, err := neteaseApi.GetSongDetail(
n.ReqData,
ids[index:util.IntMin(index+1000, len(ids))])
if err != nil || result2.Code != 200 {
break
}
cnt = len(result2.Songs)
if cnt == 0 {
break
}
for i := 0; i < cnt; i++ {
medias = append(medias, &model.Media{
Title: result2.Songs[i].Name,
Artist: _neteaseGetArtistNames(result2.Songs[i]),
Cover: model.Picture{Url: result2.Songs[i].Al.PicUrl},
Album: result2.Songs[i].Al.Name,
Url: "",
Header: nil,
User: nil,
Meta: model.Meta{
Name: n.GetName(),
Id: strconv.Itoa(result2.Songs[i].Id),
},
})
}
}
if len(medias) == 0 {
return nil, ErrorExternalApi
}
return medias, nil
}
func (n *Netease) Search(keyword string) ([]*model.Media, error) {
rawResult, err := neteaseApi.SearchSong(
n.ReqData,
neteaseApi.SearchSongConfig{
Keyword: keyword,
Limit: 30,
Offset: 0,
})
if err != nil || rawResult.Code != 200 {
return nil, ErrorExternalApi
}
medias := make([]*model.Media, 0)
for _, song := range rawResult.Result.Songs {
artists := make([]string, 0)
for _, a := range song.Artists {
artists = append(artists, a.Name)
}
medias = append(medias, &model.Media{
Title: song.Name,
Artist: strings.Join(artists, ","),
Cover: model.Picture{},
Album: song.Album.Name,
Url: "",
Header: nil,
Meta: model.Meta{
Name: n.GetName(),
Id: strconv.Itoa(song.Id),
},
})
}
return medias, nil
}
func (n *Netease) UpdateMedia(media *model.Media) error {
result, err := neteaseApi.GetSongDetail(
n.ReqData,
[]int{util.Atoi(media.Meta.(model.Meta).Id)})
if err != nil || result.Code != 200 {
return ErrorExternalApi
}
if len(result.Songs) == 0 {
return ErrorExternalApi
}
media.Title = result.Songs[0].Name
media.Cover.Url = result.Songs[0].Al.PicUrl
media.Album = result.Songs[0].Al.Name
media.Artist = _neteaseGetArtistNames(result.Songs[0])
return nil
}
func (n *Netease) UpdateMediaUrl(media *model.Media) error {
result, err := neteaseApi.GetSongURL(
n.ReqData,
neteaseApi.SongURLConfig{Ids: []int{util.Atoi(media.Meta.(model.Meta).Id)}})
if err != nil || result.Code != 200 {
return ErrorExternalApi
}
if len(result.Data) == 0 {
return ErrorExternalApi
}
if result.Data[0].Code != 200 {
return ErrorExternalApi
}
media.Url = result.Data[0].Url
return nil
}
func (n *Netease) UpdateMediaLyric(media *model.Media) error {
result, err := neteaseApi.GetSongLyric(n.ReqData, util.Atoi(media.Meta.(model.Meta).Id))
if err != nil || result.Code != 200 {
return ErrorExternalApi
}
media.Lyric = result.Lrc.Lyric
return nil
}

View File

@@ -0,0 +1,62 @@
package provider
import (
"fmt"
neteaseApi "github.com/XiaoMengXinX/Music163Api-Go/api"
"net/http"
)
// Netease other method
func (n *Netease) UpdateStatus() {
status, _ := neteaseApi.GetLoginStatus(n.ReqData)
n.loginStatus = status
}
// IsLogin check if current cookie is a login user
func (n *Netease) IsLogin() bool {
return n.loginStatus.Profile.UserId != 0
}
func (n *Netease) Nickname() string {
return n.loginStatus.Profile.Nickname
}
func (n *Netease) GetQrLoginKey() string {
unikey, err := neteaseApi.GetQrUnikey(n.ReqData)
if err != nil {
return ""
}
return unikey.Unikey
}
func (n *Netease) GetQrLoginUrl(key string) string {
return fmt.Sprintf("https://music.163.com/login?codekey=%s", key)
}
func (n *Netease) CheckQrLogin(key string) (bool, string) {
login, h, err := neteaseApi.CheckQrLogin(n.ReqData, key)
if err != nil {
return false, ""
}
// if login.Code == 800 || login.Code == 803. login success
if login.Code != 800 && login.Code != 803 {
return false, login.Message
}
cookies := make([]*http.Cookie, 0)
for _, c := range (&http.Response{Header: h}).Cookies() {
if c.Name == "MUSIC_U" || c.Name == "__csrf" {
cookies = append(cookies, c)
}
}
n.ReqData.Cookies = cookies
return true, login.Message
}
func (n *Netease) Logout() {
n.ReqData.Cookies = []*http.Cookie{
{Name: "MUSIC_U", Value: ""},
{Name: "__csrf", Value: ""},
}
return
}

View File

@@ -0,0 +1,104 @@
package provider
import (
"AynaLivePlayer/core/adapter"
"AynaLivePlayer/core/model"
"fmt"
"testing"
)
func TestNetease_Search(t *testing.T) {
var api adapter.MediaProvider = NeteaseAPI
result, err := api.Search("染 reol")
if err != nil {
return
}
fmt.Println(result)
media := result[0]
fmt.Println(media)
err = api.UpdateMediaUrl(media)
fmt.Println(err)
fmt.Println(media.Url)
}
func TestNetease_Search2(t *testing.T) {
var api adapter.MediaProvider = NeteaseAPI
result, err := api.Search("出山")
if err != nil {
return
}
t.Log(result)
media := result[0]
t.Log(media)
err = api.UpdateMediaUrl(media)
t.Log(err)
t.Log(media.Url)
}
func TestNetease_GetMusicMeta(t *testing.T) {
var api adapter.MediaProvider = NeteaseAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "33516503",
},
}
err := api.UpdateMedia(&media)
fmt.Println(err)
if err != nil {
return
}
fmt.Println(media)
}
func TestNetease_GetMusic(t *testing.T) {
var api adapter.MediaProvider = NeteaseAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "33516503",
},
}
err := api.UpdateMedia(&media)
if err != nil {
return
}
err = api.UpdateMediaUrl(&media)
if err != nil {
return
}
fmt.Println(media)
fmt.Println(media.Url)
}
func TestNetease_GetPlaylist(t *testing.T) {
var api adapter.MediaProvider = NeteaseAPI
playlist, err := api.GetPlaylist(&model.Meta{
Name: api.GetName(),
//Id: "2520739691",
Id: "2382819181",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(playlist))
for _, media := range playlist {
fmt.Println(media.Title, media.Artist, media.Album)
}
}
func TestNetease_UpdateMediaLyric(t *testing.T) {
var api adapter.MediaProvider = NeteaseAPI
media := model.Media{
Meta: model.Meta{
Name: api.GetName(),
Id: "33516503",
},
}
err := api.UpdateMediaLyric(&media)
fmt.Println(err)
fmt.Println(media.Lyric)
}

View File

@@ -0,0 +1,65 @@
package provider
import (
"AynaLivePlayer/core/adapter"
model "AynaLivePlayer/core/model"
)
var RegisteredProviders = map[string]adapter.MediaProviderCtor{
"netease": NewNetease,
"local": NewLocalCtor,
"kuwo": NewKuwo,
"bilibili": NewBilibili,
"bilibili-video": NewBilibiliVideo,
}
var Providers map[string]adapter.MediaProvider = make(map[string]adapter.MediaProvider)
func GetPlaylist(meta *model.Meta) ([]*model.Media, error) {
if v, ok := Providers[meta.Name]; ok {
return v.GetPlaylist(meta)
}
return nil, ErrorNoSuchProvider
}
func FormatPlaylistUrl(pname, uri string) (string, error) {
if v, ok := Providers[pname]; ok {
return v.FormatPlaylistUrl(uri), nil
}
return "", ErrorNoSuchProvider
}
func MatchMedia(provider string, keyword string) *model.Media {
if v, ok := Providers[provider]; ok {
return v.MatchMedia(keyword)
}
return nil
}
func Search(provider string, keyword string) ([]*model.Media, error) {
if v, ok := Providers[provider]; ok {
return v.Search(keyword)
}
return nil, ErrorNoSuchProvider
}
func UpdateMedia(media *model.Media) error {
if v, ok := Providers[media.Meta.(model.Meta).Name]; ok {
return v.UpdateMedia(media)
}
return ErrorNoSuchProvider
}
func UpdateMediaUrl(media *model.Media) error {
if v, ok := Providers[media.Meta.(model.Meta).Name]; ok {
return v.UpdateMediaUrl(media)
}
return ErrorNoSuchProvider
}
func UpdateMediaLyric(media *model.Media) error {
if v, ok := Providers[media.Meta.(model.Meta).Name]; ok {
return v.UpdateMediaLyric(media)
}
return ErrorNoSuchProvider
}

26
adapters/provider/util.go Normal file
View File

@@ -0,0 +1,26 @@
package provider
import (
"AynaLivePlayer/common/util"
"AynaLivePlayer/core/model"
"sort"
)
func MediaSort(keyword string, medias []*model.Media) {
mediaDist := make([]struct {
media *model.Media
dist int
}, len(medias))
for i, media := range medias {
mediaDist[i].media = media
mediaDist[i].dist = util.StrLen(util.LongestCommonString(keyword, media.Title)) +
util.StrLen(util.LongestCommonString(keyword, media.Artist))
}
sort.Slice(mediaDist, func(i, j int) bool {
return mediaDist[i].dist > mediaDist[j].dist
})
for i, media := range mediaDist {
medias[i] = media.media
}
return
}