Files
music-kraken/internal/plugin/interface.go
2025-10-10 14:07:03 +02:00

265 lines
5.6 KiB
Go

package plugin
import (
"errors"
"regexp"
"gitea.elara.ws/Hazel/music-kraken/internal/common"
"gitea.elara.ws/Hazel/music-kraken/internal/data"
)
type Plugin interface {
Name() string
Regex() *regexp.Regexp
RegexArtist() *regexp.Regexp
RegexAlbum() *regexp.Regexp
RegexSong() *regexp.Regexp
Init(data.SourceType)
Search(query common.Query) ([]data.MusicObject, error)
FetchArtist(source data.Source) (data.Artist, error)
FetchAlbum(source data.Source) (data.Album, error)
FetchSong(source data.Source) (data.Song, error)
}
var namePlugins = map[string]Plugin{}
var NameSourceType = map[string]data.SourceType{}
func RegisterPlugin(plugin Plugin) error {
name := plugin.Name()
if _, ok := namePlugins[name]; ok {
return errors.New("plugin " + name + " is already registered")
}
NameSourceType[name] = data.SourceType{
Name: name,
Regex: plugin.Regex(),
RegexArtist: plugin.RegexArtist(),
RegexAlbum: plugin.RegexAlbum(),
RegexSong: plugin.RegexSong(),
}
namePlugins[name] = plugin
plugin.Init(NameSourceType[name])
return nil
}
func compileSourceType(source data.Source) (data.Source, error) {
if source.SourceType != nil {
if _, ok := namePlugins[source.SourceType.Name]; !ok {
return source, errors.New("source type " + source.SourceType.Name + " not found")
}
return source, nil
}
for _, st := range NameSourceType {
if m := st.Regex.FindString(source.Url); m != "" {
source.Url = m
source.SourceType = &st
return source, nil
}
}
return source, errors.New("couldn't find source type for " + source.Url)
}
func compileSource(source data.Source) (data.Source, error) {
source, err := compileSourceType(source)
if err != nil {
return source, err
}
// find what object this source corresponds to
if source.ObjectType != "" {
return source, nil
}
sourceType := source.SourceType
if sourceType.RegexSong.MatchString(source.Url) {
source.ObjectType = data.SongSource
return source, nil
}
if sourceType.RegexAlbum.MatchString(source.Url) {
source.ObjectType = data.AlbumSource
return source, nil
}
if sourceType.RegexArtist.MatchString(source.Url) {
source.ObjectType = data.ArtistSource
return source, nil
}
return source, errors.New("couldn't find corresponding object source on " + sourceType.Name + " for " + source.Url)
}
func FetchSource(source data.Source) (data.MusicObject, error) {
// the fetch function without the post processing of the music objects
source, err := compileSource(source)
if err != nil {
return nil, err
}
plugin, ok := namePlugins[source.SourceType.Name]
if !ok {
return nil, errors.New("didn't find plugin of the name " + source.SourceType.Name)
}
if source.ObjectType == data.SongSource {
song, err := plugin.FetchSong(source)
if err != nil {
return song, err
}
song.Sources = append(song.Sources, source)
song = song.Compile().(data.Song)
return song, nil
}
if source.ObjectType == data.AlbumSource {
album, err := plugin.FetchAlbum(source)
if err != nil {
return album, err
}
album.Sources = append(album.Sources, source)
album = album.Compile().(data.Album)
return album, nil
}
if source.ObjectType == data.ArtistSource {
artist, err := plugin.FetchArtist(source)
if err != nil {
return artist, err
}
artist.Sources = append(artist.Sources, source)
artist = artist.Compile().(data.Artist)
return artist, nil
}
return nil, nil
}
func Fetch(musicObject data.MusicObject) (data.MusicObject, error) {
sources := musicObject.GetSources()
if len(sources) <= 0 {
return musicObject, errors.New("didn't find a source for object")
}
for _, source := range sources {
newMusicObject, err := FetchSource(source)
if err != nil {
return musicObject, err
}
musicObject = musicObject.Merge(newMusicObject)
}
return musicObject, nil
}
type SearchConfig struct {
IgnoreErrors bool
}
func Search(search string, config SearchConfig) ([]data.MusicObject, error) {
query, err := common.NewQuery(search)
res := []data.MusicObject{}
if err != nil && !config.IgnoreErrors {
return res, err
}
for _, plugin := range namePlugins {
s, err := plugin.Search(query)
res = append(res, s...)
if err != nil && !config.IgnoreErrors {
return res, err
}
}
return res, nil
}
type downloadState struct {
artist *data.Artist
album *data.Album
song *data.Song
}
var variousArtist = data.Artist{
Name: "VariousArtist",
}.Compile().(data.Artist)
var variousAlbum = data.Album{
Name: "VariousAlbum",
}.Compile().(data.Album)
func downloadSong(song data.Song, state downloadState) {}
func Download(musicObject data.MusicObject, statesInput ...downloadState) {
state := downloadState{}
if len(statesInput) > 0 {
state = statesInput[0]
}
musicObject = musicObject.Compile()
if song, ok := musicObject.(data.Song); ok {
state.song = &song
if state.artist == nil {
if len(song.Artists) > 0 {
state.artist = &song.Artists[0]
} else {
state.artist = &variousArtist
}
}
if state.album == nil {
if song.Album.Name != "" {
state.album = &song.Album
} else {
state.album = &variousAlbum
}
}
downloadSong(song, state)
return
}
if album, ok := musicObject.(data.Album); ok {
state.album = &album
if state.artist == nil {
if len(album.Artists) > 0 {
state.artist = &album.Artists[0]
} else {
state.artist = &variousArtist
}
}
for _, song := range album.Songs {
Download(song, state)
}
return
}
if artist, ok := musicObject.(data.Artist); ok {
state.artist = &artist
for _, album := range artist.Albums {
Download(album, state)
}
return
}
}