Compare commits
31 Commits
3dd03491ca
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
964512de06 | ||
|
|
87e276de3d | ||
|
|
04e8378f3f | ||
|
|
293de8b473 | ||
|
|
82c541653c | ||
|
|
1ff37517a5 | ||
|
|
c7fceb277d | ||
|
|
8896cb7d09 | ||
|
|
c2794367b3 | ||
|
|
16318dac20 | ||
|
|
655087fa42 | ||
|
|
19185f38a3 | ||
|
|
81d29e4d8b | ||
|
|
3565f14181 | ||
|
|
1ae859b9b9 | ||
|
|
4c0f19b257 | ||
|
|
6a1baa9eed | ||
|
|
d74a324999 | ||
|
|
184ffdf2b8 | ||
|
|
b5a78eb5b9 | ||
|
|
07468e5b4f | ||
|
|
fe244c7e68 | ||
|
|
e79e364ff9 | ||
|
|
6bd957b09e | ||
|
|
4eab2bb874 | ||
|
|
e9d1d20c63 | ||
|
|
9a461a0c16 | ||
|
|
5be641cbfb | ||
|
|
960598971e | ||
|
|
6ce1eb5f92 | ||
|
|
9c7dfd5775 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
7
go.mod
7
go.mod
@@ -3,7 +3,8 @@ module gitea.elara.ws/Hazel/music-kraken
|
|||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
github.com/PuerkitoBio/goquery v1.10.3
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
golang.org/x/net v0.45.0
|
||||||
golang.org/x/net v0.45.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
|
|||||||
@@ -2,24 +2,20 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitea.elara.ws/Hazel/music-kraken/internal/common"
|
||||||
|
"gitea.elara.ws/Hazel/music-kraken/internal/common/color"
|
||||||
"gitea.elara.ws/Hazel/music-kraken/internal/data"
|
"gitea.elara.ws/Hazel/music-kraken/internal/data"
|
||||||
"gitea.elara.ws/Hazel/music-kraken/internal/plugin"
|
"gitea.elara.ws/Hazel/music-kraken/internal/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func zeroPad(num int, length int) string {
|
|
||||||
str := strconv.Itoa(num)
|
|
||||||
if len(str) >= length {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
return strings.Repeat("0", length-len(str)) + str
|
|
||||||
}
|
|
||||||
|
|
||||||
func printResults(musicObjects []data.MusicObject) {
|
func printResults(musicObjects []data.MusicObject) {
|
||||||
if len(musicObjects) <= 0 {
|
if len(musicObjects) <= 0 {
|
||||||
return
|
return
|
||||||
@@ -28,7 +24,7 @@ func printResults(musicObjects []data.MusicObject) {
|
|||||||
results := make([]string, len(musicObjects))
|
results := make([]string, len(musicObjects))
|
||||||
|
|
||||||
for i, m := range musicObjects {
|
for i, m := range musicObjects {
|
||||||
results[i] = zeroPad(i, 2) + ": "
|
results[i] = common.ZeroPad(i, 2) + ": "
|
||||||
|
|
||||||
if a, ok := m.(data.Artist); ok {
|
if a, ok := m.(data.Artist); ok {
|
||||||
results[i] += "#a " + a.Name
|
results[i] += "#a " + a.Name
|
||||||
@@ -50,38 +46,148 @@ func printResults(musicObjects []data.MusicObject) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, source := range m.GetSources() {
|
sources := m.GetSources()
|
||||||
results[i] += "\n\t- " + source.Url
|
if len(sources) > 0 {
|
||||||
|
for _, source := range sources {
|
||||||
|
results[i] += "\n\t- " + source.SourceType.Name + " " + string(source.ObjectType) + " " + source.Url
|
||||||
|
if source.AudioUrl != "" {
|
||||||
|
results[i] += "\n\t " + source.AudioUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results[i] = color.StrikeThrough + results[i] + color.Reset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
fmt.Println(strings.Join(results, "\n"))
|
fmt.Println(strings.Join(results, "\n"))
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Shell() {
|
type musicObjectStore [][]data.MusicObject
|
||||||
|
|
||||||
|
func (s musicObjectStore) currentMusicObjects() ([]data.MusicObject, error) {
|
||||||
|
if len(s) <= 0 {
|
||||||
|
return []data.MusicObject{}, errors.New("no items to select from")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (s)[len(s)-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexSelectionPattern = regexp.MustCompile(`^[\d ,]+$`)
|
||||||
|
|
||||||
|
func interpretCommand(command string, store musicObjectStore) (musicObjectStore, []error) {
|
||||||
|
// going back in history
|
||||||
|
if command == ".." {
|
||||||
|
if len(store) <= 1 {
|
||||||
|
return store, []error{errors.New("can't go back")}
|
||||||
|
}
|
||||||
|
|
||||||
|
return store[:len(store)-1], []error{}
|
||||||
|
}
|
||||||
|
|
||||||
|
forceDownload := false
|
||||||
|
if strings.HasPrefix(command, "d:") {
|
||||||
|
command, _ = strings.CutPrefix(command, "d:")
|
||||||
|
command = strings.TrimSpace(command)
|
||||||
|
|
||||||
|
forceDownload = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch special music object
|
||||||
|
if indexSelectionPattern.MatchString(command) {
|
||||||
|
currentMusicObjects, err := store.currentMusicObjects()
|
||||||
|
if err != nil {
|
||||||
|
return store, []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fetched data.MusicObject
|
||||||
|
|
||||||
|
for _, stringIndex := range strings.Split(command, ",") {
|
||||||
|
index, _ := strconv.Atoi(strings.TrimSpace(stringIndex))
|
||||||
|
|
||||||
|
if index >= len(currentMusicObjects) || index < 0 {
|
||||||
|
return store, []error{errors.New(command + " is out of bounds [0 <= " + strconv.Itoa(index) + " <= " + strconv.Itoa(len(currentMusicObjects)-1) + "]")}
|
||||||
|
}
|
||||||
|
|
||||||
|
current := currentMusicObjects[index]
|
||||||
|
|
||||||
|
newFetched, err := plugin.Fetch(current)
|
||||||
|
if err != nil {
|
||||||
|
return store, []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fetched == nil {
|
||||||
|
fetched = newFetched
|
||||||
|
} else {
|
||||||
|
fetched = fetched.Merge(newFetched)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if forceDownload {
|
||||||
|
return store, plugin.Download(fetched)
|
||||||
|
} else {
|
||||||
|
return append(store, fetched.Related()), []error{}
|
||||||
|
}
|
||||||
|
} else if forceDownload {
|
||||||
|
return store, []error{errors.New("can only download indices not " + command)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// search in every other case
|
||||||
|
currentMusicObjects, err := plugin.Search(command, plugin.SearchConfig{IgnoreErrors: false})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(store, currentMusicObjects), []error{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Shell(commandsList ...[]string) {
|
||||||
plugin.RegisterPlugin(&plugin.Musify{})
|
plugin.RegisterPlugin(&plugin.Musify{})
|
||||||
|
|
||||||
|
commands := []string{}
|
||||||
|
if len(commandsList) > 0 {
|
||||||
|
commands = commandsList[0]
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("== MusicKraken Shell ==")
|
fmt.Println("== MusicKraken Shell ==")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
for {
|
store := musicObjectStore{}
|
||||||
fmt.Print("> ")
|
var err error = nil
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
for {
|
||||||
line, err := reader.ReadString('\n')
|
var command string
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
if len(commands) <= 0 {
|
||||||
return
|
fmt.Print("> ")
|
||||||
|
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
command, err = reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
command = commands[0]
|
||||||
|
commands = commands[1:]
|
||||||
|
|
||||||
|
fmt.Println("> " + command)
|
||||||
}
|
}
|
||||||
|
|
||||||
searchResults, err := plugin.Search(line, plugin.SearchConfig{IgnoreErrors: false})
|
var errList []error
|
||||||
if err != nil {
|
store, errList = interpretCommand(strings.TrimSpace(command), store)
|
||||||
fmt.Println(err)
|
if len(errList) > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
for _, err := range errList {
|
||||||
|
fmt.Println(color.Yellow + err.Error() + color.Reset)
|
||||||
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
currentMusicObject, err := store.currentMusicObjects()
|
||||||
printResults(searchResults)
|
if err == nil {
|
||||||
|
printResults(currentMusicObject)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
87
internal/common/color/color.go
Normal file
87
internal/common/color/color.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package color
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Reset = "\033[0m"
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// Special //
|
||||||
|
/////////////
|
||||||
|
|
||||||
|
Bold = "\033[1m"
|
||||||
|
Underline = "\033[4m"
|
||||||
|
StrikeThrough = "\033[9m"
|
||||||
|
|
||||||
|
/////////////////
|
||||||
|
// Text colors //
|
||||||
|
/////////////////
|
||||||
|
|
||||||
|
Black = "\033[30m"
|
||||||
|
Red = "\033[31m"
|
||||||
|
Green = "\033[32m"
|
||||||
|
Yellow = "\033[33m"
|
||||||
|
Blue = "\033[34m"
|
||||||
|
Purple = "\033[35m"
|
||||||
|
Cyan = "\033[36m"
|
||||||
|
Gray = "\033[37m"
|
||||||
|
White = "\033[97m"
|
||||||
|
|
||||||
|
///////////////////////
|
||||||
|
// Background colors //
|
||||||
|
///////////////////////
|
||||||
|
|
||||||
|
BlackBackground = "\033[40m"
|
||||||
|
RedBackground = "\033[41m"
|
||||||
|
GreenBackground = "\033[42m"
|
||||||
|
YellowBackground = "\033[43m"
|
||||||
|
BlueBackground = "\033[44m"
|
||||||
|
PurpleBackground = "\033[45m"
|
||||||
|
CyanBackground = "\033[46m"
|
||||||
|
GrayBackground = "\033[47m"
|
||||||
|
WhiteBackground = "\033[107m"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
Reset = ""
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// Special //
|
||||||
|
/////////////
|
||||||
|
|
||||||
|
Bold = ""
|
||||||
|
Underline = ""
|
||||||
|
StrikeThrough = ""
|
||||||
|
|
||||||
|
/////////////////
|
||||||
|
// Text colors //
|
||||||
|
/////////////////
|
||||||
|
|
||||||
|
Black = ""
|
||||||
|
Red = ""
|
||||||
|
Green = ""
|
||||||
|
Yellow = ""
|
||||||
|
Blue = ""
|
||||||
|
Purple = ""
|
||||||
|
Cyan = ""
|
||||||
|
Gray = ""
|
||||||
|
White = ""
|
||||||
|
|
||||||
|
///////////////////////
|
||||||
|
// Background colors //
|
||||||
|
///////////////////////
|
||||||
|
|
||||||
|
BlackBackground = ""
|
||||||
|
RedBackground = ""
|
||||||
|
GreenBackground = ""
|
||||||
|
YellowBackground = ""
|
||||||
|
BlueBackground = ""
|
||||||
|
PurpleBackground = ""
|
||||||
|
CyanBackground = ""
|
||||||
|
GrayBackground = ""
|
||||||
|
WhiteBackground = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,3 +13,112 @@ func Unify(s string) string {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ZeroPad(num int, length int) string {
|
||||||
|
str := strconv.Itoa(num)
|
||||||
|
if len(str) >= length {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return strings.Repeat("0", length-len(str)) + str
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNumeric(num string) bool {
|
||||||
|
for _, c := range num {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var commonTitleSuffix = []string{"(official video)"}
|
||||||
|
|
||||||
|
const openBrackets = "(["
|
||||||
|
const closeBrackets = ")]"
|
||||||
|
|
||||||
|
var forbiddenSubstringInBrackets = []string{"official", "video", "audio", "lyrics", "prod", "remix", "ft", "feat", "ft.", "feat."}
|
||||||
|
|
||||||
|
func CleanSongTitle(title string, artistName string) string {
|
||||||
|
/*
|
||||||
|
# remove brackets and their content if they contain disallowed substrings
|
||||||
|
for open_bracket, close_bracket in zip(OPEN_BRACKETS, CLOSE_BRACKETS):
|
||||||
|
if open_bracket not in raw_song_title or close_bracket not in raw_song_title:
|
||||||
|
continue
|
||||||
|
|
||||||
|
start = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
open_bracket_index = raw_song_title.index(open_bracket, start)
|
||||||
|
except ValueError:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
close_bracket_index = raw_song_title.index(close_bracket, open_bracket_index + 1)
|
||||||
|
except ValueError:
|
||||||
|
break
|
||||||
|
|
||||||
|
substring = raw_song_title[open_bracket_index + 1:close_bracket_index]
|
||||||
|
if any(disallowed_substring in substring.lower() for disallowed_substring in DISALLOWED_SUBSTRING_IN_BRACKETS):
|
||||||
|
raw_song_title = raw_song_title[:open_bracket_index] + raw_song_title[close_bracket_index + 1:]
|
||||||
|
else:
|
||||||
|
start = close_bracket_index + 1
|
||||||
|
|
||||||
|
# everything that requires the artist name
|
||||||
|
if artist_name is not None:
|
||||||
|
artist_name = artist_name.strip()
|
||||||
|
|
||||||
|
# Remove artist from the start of the title
|
||||||
|
if raw_song_title.lower().startswith(artist_name.lower()):
|
||||||
|
|
||||||
|
possible_new_name = raw_song_title[len(artist_name):].strip()
|
||||||
|
|
||||||
|
for char in ("-", "–", ":", "|"):
|
||||||
|
if possible_new_name.startswith(char):
|
||||||
|
raw_song_title = possible_new_name[1:].strip()
|
||||||
|
break
|
||||||
|
|
||||||
|
return raw_song_title.strip()
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
title = strings.TrimSpace(title)
|
||||||
|
|
||||||
|
for _, d := range commonTitleSuffix {
|
||||||
|
if strings.HasSuffix(strings.ToLower(title), d) {
|
||||||
|
title = strings.TrimSpace(title[:len(d)-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for b, open := range openBrackets {
|
||||||
|
close := closeBrackets[b]
|
||||||
|
|
||||||
|
s := -1
|
||||||
|
e := -1
|
||||||
|
|
||||||
|
for i, c := range title {
|
||||||
|
if c == open {
|
||||||
|
s = i
|
||||||
|
} else if c == rune(close) {
|
||||||
|
e = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove := false
|
||||||
|
if s > -1 {
|
||||||
|
substring := title[s:e]
|
||||||
|
|
||||||
|
for _, f := range forbiddenSubstringInBrackets {
|
||||||
|
if strings.Contains(substring, f) {
|
||||||
|
remove = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
title = title[:s] + title[e:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,3 +35,18 @@ func TestUnify(t *testing.T) {
|
|||||||
t.Errorf(`Double whitespaces need to be removed`)
|
t.Errorf(`Double whitespaces need to be removed`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestZeroPad(t *testing.T) {
|
||||||
|
cases := map[int]string{
|
||||||
|
0: "000",
|
||||||
|
5: "005",
|
||||||
|
1000: "1000",
|
||||||
|
50: "050",
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range cases {
|
||||||
|
if res := ZeroPad(key, 3); res != val {
|
||||||
|
t.Errorf(`did not match`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type MusicObject interface {
|
|||||||
Compile() MusicObject
|
Compile() MusicObject
|
||||||
GetIndices() []string
|
GetIndices() []string
|
||||||
Merge(other MusicObject) MusicObject
|
Merge(other MusicObject) MusicObject
|
||||||
|
Related() []MusicObject
|
||||||
}
|
}
|
||||||
|
|
||||||
func dedupeMusicObjects[T MusicObject](inputMusicObjects []T) []T {
|
func dedupeMusicObjects[T MusicObject](inputMusicObjects []T) []T {
|
||||||
@@ -71,6 +72,20 @@ type Song struct {
|
|||||||
Sources []Source
|
Sources []Source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Song) Related() []MusicObject {
|
||||||
|
res := []MusicObject{}
|
||||||
|
|
||||||
|
for _, a := range m.Artists {
|
||||||
|
res = append(res, a)
|
||||||
|
}
|
||||||
|
if m.Album.Name != "" {
|
||||||
|
res = append(res, m.Album)
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, m)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (m Song) GetSources() []Source {
|
func (m Song) GetSources() []Source {
|
||||||
return m.Sources
|
return m.Sources
|
||||||
}
|
}
|
||||||
@@ -127,6 +142,20 @@ type Album struct {
|
|||||||
Sources []Source
|
Sources []Source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Album) Related() []MusicObject {
|
||||||
|
res := []MusicObject{}
|
||||||
|
|
||||||
|
for _, a := range m.Artists {
|
||||||
|
res = append(res, a)
|
||||||
|
}
|
||||||
|
res = append(res, m)
|
||||||
|
for _, a := range m.Songs {
|
||||||
|
res = append(res, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (m Album) GetSources() []Source {
|
func (m Album) GetSources() []Source {
|
||||||
return m.Sources
|
return m.Sources
|
||||||
}
|
}
|
||||||
@@ -183,6 +212,16 @@ type Artist struct {
|
|||||||
Sources []Source
|
Sources []Source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Artist) Related() []MusicObject {
|
||||||
|
res := []MusicObject{m}
|
||||||
|
|
||||||
|
for _, a := range m.Albums {
|
||||||
|
res = append(res, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (m Artist) GetSources() []Source {
|
func (m Artist) GetSources() []Source {
|
||||||
return m.Sources
|
return m.Sources
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ const AlbumSource = ObjectType("album")
|
|||||||
const ArtistSource = ObjectType("artist")
|
const ArtistSource = ObjectType("artist")
|
||||||
|
|
||||||
type Source struct {
|
type Source struct {
|
||||||
Url string
|
Url string
|
||||||
|
AudioUrl string // will only be used when ObjectType = SongSource
|
||||||
|
|
||||||
SourceType *SourceType
|
SourceType *SourceType
|
||||||
ObjectType ObjectType
|
ObjectType ObjectType
|
||||||
}
|
}
|
||||||
@@ -38,6 +40,9 @@ func dedupeSources(inputSources []Source) []Source {
|
|||||||
if source.SourceType != nil {
|
if source.SourceType != nil {
|
||||||
deduped[mergeWithIndex].SourceType = source.SourceType
|
deduped[mergeWithIndex].SourceType = source.SourceType
|
||||||
}
|
}
|
||||||
|
if source.AudioUrl != "" {
|
||||||
|
deduped[mergeWithIndex].AudioUrl = source.AudioUrl
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// just appending
|
// just appending
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package plugin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"gitea.elara.ws/Hazel/music-kraken/internal/common"
|
"gitea.elara.ws/Hazel/music-kraken/internal/common"
|
||||||
@@ -15,7 +16,7 @@ type Plugin interface {
|
|||||||
RegexAlbum() *regexp.Regexp
|
RegexAlbum() *regexp.Regexp
|
||||||
RegexSong() *regexp.Regexp
|
RegexSong() *regexp.Regexp
|
||||||
|
|
||||||
Init()
|
Init(data.SourceType)
|
||||||
|
|
||||||
Search(query common.Query) ([]data.MusicObject, error)
|
Search(query common.Query) ([]data.MusicObject, error)
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ func RegisterPlugin(plugin Plugin) error {
|
|||||||
|
|
||||||
namePlugins[name] = plugin
|
namePlugins[name] = plugin
|
||||||
|
|
||||||
plugin.Init()
|
plugin.Init(NameSourceType[name])
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -99,7 +100,7 @@ func compileSource(source data.Source) (data.Source, error) {
|
|||||||
return source, errors.New("couldn't find corresponding object source on " + sourceType.Name + " for " + source.Url)
|
return source, errors.New("couldn't find corresponding object source on " + sourceType.Name + " for " + source.Url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Fetch(source data.Source) (data.MusicObject, error) {
|
func FetchSource(source data.Source) (data.MusicObject, error) {
|
||||||
// the fetch function without the post processing of the music objects
|
// the fetch function without the post processing of the music objects
|
||||||
source, err := compileSource(source)
|
source, err := compileSource(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -147,6 +148,25 @@ func Fetch(source data.Source) (data.MusicObject, error) {
|
|||||||
return nil, 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.Compile(), nil
|
||||||
|
}
|
||||||
|
|
||||||
type SearchConfig struct {
|
type SearchConfig struct {
|
||||||
IgnoreErrors bool
|
IgnoreErrors bool
|
||||||
}
|
}
|
||||||
@@ -170,3 +190,93 @@ func Search(search string, config SearchConfig) ([]data.MusicObject, error) {
|
|||||||
|
|
||||||
return res, nil
|
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) error {
|
||||||
|
fmt.Println("downloading: " + song.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Download(musicObject data.MusicObject, statesInput ...downloadState) []error {
|
||||||
|
state := downloadState{}
|
||||||
|
if len(statesInput) > 0 {
|
||||||
|
state = statesInput[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
musicObject, err := Fetch(musicObject)
|
||||||
|
if err != nil {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := downloadSong(song, state)
|
||||||
|
if err == nil {
|
||||||
|
return []error{}
|
||||||
|
} else {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errList := []error{}
|
||||||
|
for _, song := range album.Songs {
|
||||||
|
errList = append(errList, Download(song, state)...)
|
||||||
|
}
|
||||||
|
return errList
|
||||||
|
}
|
||||||
|
|
||||||
|
if artist, ok := musicObject.(data.Artist); ok {
|
||||||
|
state.artist = &artist
|
||||||
|
|
||||||
|
errList := []error{}
|
||||||
|
for _, album := range artist.Albums {
|
||||||
|
errList = append(errList, Download(album, state)...)
|
||||||
|
}
|
||||||
|
return errList
|
||||||
|
}
|
||||||
|
|
||||||
|
return []error{
|
||||||
|
errors.New("music object not recognized"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package plugin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -22,7 +24,8 @@ func extractName(s string) string {
|
|||||||
const musifyHost = "https://musify.club"
|
const musifyHost = "https://musify.club"
|
||||||
|
|
||||||
type Musify struct {
|
type Musify struct {
|
||||||
session *scraper.Session
|
session *scraper.Session
|
||||||
|
sourceType data.SourceType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Musify) Name() string {
|
func (m Musify) Name() string {
|
||||||
@@ -41,15 +44,16 @@ func (m Musify) RegexAlbum() *regexp.Regexp {
|
|||||||
return regexp.MustCompile(`(?i)https?://musify\.club/release/[a-z\-0-9]+`)
|
return regexp.MustCompile(`(?i)https?://musify\.club/release/[a-z\-0-9]+`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Musify) Init() {
|
func (m *Musify) Init(sourceType data.SourceType) {
|
||||||
m.session = scraper.NewSession()
|
m.session = scraper.NewSession()
|
||||||
|
m.sourceType = sourceType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Musify) RegexSong() *regexp.Regexp {
|
func (m Musify) RegexSong() *regexp.Regexp {
|
||||||
return regexp.MustCompile(`(?i)https?://musify\.club/track/[a-z\-0-9]+`)
|
return regexp.MustCompile(`(?i)https?://musify\.club/track/[a-z\-0-9]+`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseArtistContact(contact *goquery.Selection) (data.Artist, error) {
|
func (m Musify) parseArtistContact(contact *goquery.Selection) (data.Artist, error) {
|
||||||
artist := data.Artist{}
|
artist := data.Artist{}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -61,6 +65,7 @@ func parseArtistContact(contact *goquery.Selection) (data.Artist, error) {
|
|||||||
artist.Sources = append(artist.Sources, data.Source{
|
artist.Sources = append(artist.Sources, data.Source{
|
||||||
Url: musifyHost + url,
|
Url: musifyHost + url,
|
||||||
ObjectType: data.ArtistSource,
|
ObjectType: data.ArtistSource,
|
||||||
|
SourceType: &m.sourceType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +90,7 @@ func parseArtistContact(contact *goquery.Selection) (data.Artist, error) {
|
|||||||
return artist, err
|
return artist, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAlbumContact(contact *goquery.Selection) (data.Album, error) {
|
func (m Musify) parseAlbumContact(contact *goquery.Selection) (data.Album, error) {
|
||||||
album := data.Album{}
|
album := data.Album{}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -113,7 +118,8 @@ func parseAlbumContact(contact *goquery.Selection) (data.Album, error) {
|
|||||||
if url, urlExists := anchor.Attr("href"); urlExists {
|
if url, urlExists := anchor.Attr("href"); urlExists {
|
||||||
album.Sources = append(album.Sources, data.Source{
|
album.Sources = append(album.Sources, data.Source{
|
||||||
Url: musifyHost + url,
|
Url: musifyHost + url,
|
||||||
ObjectType: data.ArtistSource,
|
ObjectType: data.AlbumSource,
|
||||||
|
SourceType: &m.sourceType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +173,7 @@ func parseAlbumContact(contact *goquery.Selection) (data.Album, error) {
|
|||||||
return album, err
|
return album, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseContactContainer(contactContainer *goquery.Selection) []data.MusicObject {
|
func (m Musify) parseContactContainer(contactContainer *goquery.Selection) []data.MusicObject {
|
||||||
res := []data.MusicObject{}
|
res := []data.MusicObject{}
|
||||||
|
|
||||||
contactContainer.Find("div.contacts__item").Each(func(i int, contact *goquery.Selection) {
|
contactContainer.Find("div.contacts__item").Each(func(i int, contact *goquery.Selection) {
|
||||||
@@ -178,11 +184,11 @@ func parseContactContainer(contactContainer *goquery.Selection) []data.MusicObje
|
|||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
if strings.Contains(url, "artist") {
|
if strings.Contains(url, "artist") {
|
||||||
if artist, err := parseArtistContact(contact); err == nil {
|
if artist, err := m.parseArtistContact(contact); err == nil {
|
||||||
res = append(res, artist)
|
res = append(res, artist)
|
||||||
}
|
}
|
||||||
} else if strings.Contains(url, "release") {
|
} else if strings.Contains(url, "release") {
|
||||||
if album, err := parseAlbumContact(contact); err == nil {
|
if album, err := m.parseAlbumContact(contact); err == nil {
|
||||||
res = append(res, album)
|
res = append(res, album)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +199,7 @@ func parseContactContainer(contactContainer *goquery.Selection) []data.MusicObje
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePlaylistItem(playlistItem *goquery.Selection) (data.Song, error) {
|
func (m Musify) parsePlaylistItem(playlistItem *goquery.Selection) (data.Song, error) {
|
||||||
song := data.Song{}
|
song := data.Song{}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -211,7 +217,7 @@ func parsePlaylistItem(playlistItem *goquery.Selection) (data.Song, error) {
|
|||||||
song.Artists = append(song.Artists, data.Artist{
|
song.Artists = append(song.Artists, data.Artist{
|
||||||
Name: strings.TrimSpace(artistAnchor.Text()),
|
Name: strings.TrimSpace(artistAnchor.Text()),
|
||||||
Sources: []data.Source{
|
Sources: []data.Source{
|
||||||
{Url: musifyHost + url, ObjectType: data.ArtistSource},
|
{Url: musifyHost + url, ObjectType: data.ArtistSource, SourceType: &m.sourceType},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -224,6 +230,7 @@ func parsePlaylistItem(playlistItem *goquery.Selection) (data.Song, error) {
|
|||||||
song.Sources = append(song.Sources, data.Source{
|
song.Sources = append(song.Sources, data.Source{
|
||||||
Url: musifyHost + href,
|
Url: musifyHost + href,
|
||||||
ObjectType: data.SongSource,
|
ObjectType: data.SongSource,
|
||||||
|
SourceType: &m.sourceType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,11 +242,11 @@ func parsePlaylistItem(playlistItem *goquery.Selection) (data.Song, error) {
|
|||||||
return song, err
|
return song, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePlaylist(playlist *goquery.Selection) []data.MusicObject {
|
func (m Musify) parsePlaylist(playlist *goquery.Selection) []data.MusicObject {
|
||||||
res := []data.MusicObject{}
|
res := []data.MusicObject{}
|
||||||
|
|
||||||
playlist.Find("div.playlist__item").Each(func(i int, playlistItem *goquery.Selection) {
|
playlist.Find("div.playlist__item").Each(func(i int, playlistItem *goquery.Selection) {
|
||||||
if song, err := parsePlaylistItem(playlistItem); err == nil {
|
if song, err := m.parsePlaylistItem(playlistItem); err == nil {
|
||||||
res = append(res, song)
|
res = append(res, song)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -250,7 +257,7 @@ func parsePlaylist(playlist *goquery.Selection) []data.MusicObject {
|
|||||||
func (m *Musify) Search(query common.Query) ([]data.MusicObject, error) {
|
func (m *Musify) Search(query common.Query) ([]data.MusicObject, error) {
|
||||||
musicObjects := []data.MusicObject{}
|
musicObjects := []data.MusicObject{}
|
||||||
|
|
||||||
resp, err := m.session.PostMultipartForm("https://musify.club/en/search", map[string]string{
|
resp, err := m.session.PostMultipartForm("https://musify.club/search", map[string]string{
|
||||||
"SearchText": query.Search, // alternatively I could also add year and genre
|
"SearchText": query.Search, // alternatively I could also add year and genre
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -263,30 +270,674 @@ func (m *Musify) Search(query common.Query) ([]data.MusicObject, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
doc.Find("div.contacts").Each(func(i int, contactContainer *goquery.Selection) {
|
doc.Find("div.contacts").Each(func(i int, contactContainer *goquery.Selection) {
|
||||||
musicObjects = append(musicObjects, parseContactContainer(contactContainer)...)
|
musicObjects = append(musicObjects, m.parseContactContainer(contactContainer)...)
|
||||||
})
|
})
|
||||||
|
|
||||||
doc.Find("div.playlist").Each(func(i int, playlist *goquery.Selection) {
|
doc.Find("div.playlist").Each(func(i int, playlist *goquery.Selection) {
|
||||||
musicObjects = append(musicObjects, parsePlaylist(playlist)...)
|
musicObjects = append(musicObjects, m.parsePlaylist(playlist)...)
|
||||||
})
|
})
|
||||||
|
|
||||||
return musicObjects, nil
|
return musicObjects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Musify) FetchSong(source data.Source) (data.Song, error) {
|
type parsedSongUrl struct {
|
||||||
return data.Song{
|
id string
|
||||||
Name: extractName(source.Url),
|
name string
|
||||||
}, nil
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParsedSongUrl(rawUrl string) (parsedSongUrl, error) {
|
||||||
|
res := parsedSongUrl{
|
||||||
|
url: rawUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := url.Parse(rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs := strings.Split(parsed.Path, "/")
|
||||||
|
correctPart := dirs[len(dirs)-1]
|
||||||
|
split := strings.Split(correctPart, "-")
|
||||||
|
if len(split) < 2 {
|
||||||
|
return res, errors.New("last part of path has to consist of at least one - " + correctPart)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.id = strings.TrimSpace(split[len(split)-1])
|
||||||
|
res.name = strings.Join(split[:len(split)-1], "-")
|
||||||
|
|
||||||
|
if !common.IsNumeric(res.id) {
|
||||||
|
return res, errors.New("last elem (id) has to be numeric " + res.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Musify) FetchSong(source data.Source) (data.Song, error) {
|
||||||
|
song := data.Song{
|
||||||
|
Sources: []data.Source{
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := m.session.Get(source.Url)
|
||||||
|
if err != nil {
|
||||||
|
return song, err
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := scraper.GetHtml(resp)
|
||||||
|
if err != nil {
|
||||||
|
return song, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download URL
|
||||||
|
doc.Find("a[itemprop='audio']").Each(func(i int, anchor *goquery.Selection) {
|
||||||
|
if href, _ := anchor.Attr("href"); true {
|
||||||
|
// will be the source first added at the begining
|
||||||
|
song.Sources[0].AudioUrl = musifyHost + href
|
||||||
|
} else {
|
||||||
|
// http://musify.club/track/dl/7141298/crystal-f-sekundenschlaf.mp3
|
||||||
|
parsed, err := newParsedSongUrl(song.Sources[0].Url)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
song.Sources[0].AudioUrl = "http://musify.club/track/dl/" + parsed.id + "/" + parsed.name + ".mp3"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Song detail
|
||||||
|
var listElement *goquery.Selection
|
||||||
|
doc.Find("ul.album-info").Each(func(i int, albumInfo *goquery.Selection) {
|
||||||
|
listElement = albumInfo.Find("li").First()
|
||||||
|
})
|
||||||
|
|
||||||
|
if listElement != nil {
|
||||||
|
listElement.Find("a").Each(func(i int, artistAnchor *goquery.Selection) {
|
||||||
|
if href, exists := artistAnchor.Attr("href"); exists {
|
||||||
|
song.Artists = append(song.Artists, data.Artist{
|
||||||
|
Name: strings.TrimSpace(artistAnchor.Text()),
|
||||||
|
Sources: []data.Source{
|
||||||
|
{Url: musifyHost + href, ObjectType: data.ArtistSource, SourceType: &m.sourceType},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breadcrumbs
|
||||||
|
if breadcrumbList := doc.Find("ol.breadcrumb"); breadcrumbList.Length() > 0 {
|
||||||
|
listPoints := breadcrumbList.Find("li.breadcrumb-item")
|
||||||
|
if listPoints.Length() != 5 {
|
||||||
|
return song, errors.New("too many breadcrumbs on page")
|
||||||
|
}
|
||||||
|
|
||||||
|
if artistAnchor := listPoints.Eq(2).Find("a"); artistAnchor != nil && artistAnchor.Length() > 0 {
|
||||||
|
artist := data.Artist{}
|
||||||
|
useArtist := true
|
||||||
|
|
||||||
|
if href, exists := artistAnchor.Attr("href"); exists {
|
||||||
|
hrefParts := strings.Split(href, "/")
|
||||||
|
if len(hrefParts) <= 1 || hrefParts[len(hrefParts)-2] != "artist" {
|
||||||
|
useArtist = false
|
||||||
|
}
|
||||||
|
|
||||||
|
artist.Sources = append(artist.Sources, data.Source{Url: musifyHost + href, ObjectType: data.ArtistSource, SourceType: &m.sourceType})
|
||||||
|
} else {
|
||||||
|
useArtist = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if nameElem := artistAnchor.Find("span[itemprop='name']"); nameElem.Length() > 0 {
|
||||||
|
artist.Name = strings.TrimSpace(nameElem.Text())
|
||||||
|
} else {
|
||||||
|
useArtist = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if useArtist {
|
||||||
|
song.Artists = append(song.Artists, artist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if albumAnchor := listPoints.Eq(3).Find("a"); albumAnchor != nil && albumAnchor.Length() > 0 {
|
||||||
|
if href, exists := albumAnchor.Attr("href"); exists {
|
||||||
|
song.Album.Sources = append(song.Album.Sources, data.Source{
|
||||||
|
Url: musifyHost + href,
|
||||||
|
ObjectType: data.AlbumSource,
|
||||||
|
SourceType: &m.sourceType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if nameElem := albumAnchor.Find("span[itemprop='name']"); nameElem.Length() > 0 {
|
||||||
|
song.Album.Name = strings.TrimSpace(nameElem.Text())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
song.Name = strings.TrimSpace(listPoints.Eq(4).Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
return song, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Musify) parseSongCard(songCard *goquery.Selection) data.Song {
|
||||||
|
song := data.Song{
|
||||||
|
Artists: []data.Artist{},
|
||||||
|
Sources: []data.Source{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get song name from data attribute
|
||||||
|
songName, _ := songCard.Attr("data-name")
|
||||||
|
song.Name = songName
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Get tracksort
|
||||||
|
tracksortSelection := songCard.Find("div.playlist__position")
|
||||||
|
if tracksortSelection.Length() > 0 {
|
||||||
|
rawTracksort := strings.TrimSpace(tracksortSelection.Text())
|
||||||
|
if parsedTracksort, err := strconv.Atoi(rawTracksort); err == nil {
|
||||||
|
tracksort = parsedTracksort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Playlist details
|
||||||
|
playlistDetails := songCard.Find("div.playlist__details")
|
||||||
|
if playlistDetails.Length() > 0 {
|
||||||
|
// Track anchor
|
||||||
|
anchorList := playlistDetails.Find("a")
|
||||||
|
if anchorList.Length() > 1 {
|
||||||
|
trackAnchor := anchorList.Last()
|
||||||
|
if href, exists := trackAnchor.Attr("href"); exists {
|
||||||
|
song.Sources = append(song.Sources, data.Source{Url: musifyHost + href, ObjectType: data.SongSource, SourceType: &m.sourceType})
|
||||||
|
}
|
||||||
|
song.Name = strings.TrimSpace(trackAnchor.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artist spans
|
||||||
|
playlistDetails.Find("span[itemprop='byArtist']").Each(func(i int, artistSpan *goquery.Selection) {
|
||||||
|
artist := data.Artist{
|
||||||
|
Sources: []data.Source{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artist URL
|
||||||
|
metaArtistSrc := artistSpan.Find("meta[itemprop='url']")
|
||||||
|
if metaArtistSrc.Length() > 0 {
|
||||||
|
if content, exists := metaArtistSrc.Attr("content"); exists && content != "" {
|
||||||
|
artist.Sources = append(artist.Sources, data.Source{
|
||||||
|
Url: musifyHost + content,
|
||||||
|
ObjectType: data.ArtistSource,
|
||||||
|
SourceType: &m.sourceType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artist name
|
||||||
|
metaArtistName := artistSpan.Find("meta[itemprop='name']")
|
||||||
|
if metaArtistName.Length() > 0 {
|
||||||
|
if content, exists := metaArtistName.Attr("content"); exists && content != "" {
|
||||||
|
artist.Name = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if artist.Name != "" || len(artist.Sources) > 0 {
|
||||||
|
song.Artists = append(song.Artists, artist)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Playlist actions - download link
|
||||||
|
playlistActions := songCard.Find("div.playlist__actions")
|
||||||
|
if playlistActions.Length() > 0 {
|
||||||
|
downloadAnchor := playlistActions.Find("a[itemprop='audio']")
|
||||||
|
if downloadAnchor.Length() > 0 {
|
||||||
|
if href, exists := downloadAnchor.Attr("href"); exists && currentURL != "" {
|
||||||
|
// Add source with audio URL
|
||||||
|
song.Sources = append(song.Sources, data.Source{
|
||||||
|
Url: currentURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return song
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Musify) parseAlbum(doc *goquery.Document) data.Album {
|
||||||
|
album := data.Album{
|
||||||
|
Artists: []data.Artist{},
|
||||||
|
Sources: []data.Source{},
|
||||||
|
Songs: []data.Song{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breadcrumb
|
||||||
|
breadcrumb := doc.Find("ol.breadcrumb")
|
||||||
|
breadcrumbElements := breadcrumb.Find("li.breadcrumb-item")
|
||||||
|
if breadcrumbElements.Length() == 4 {
|
||||||
|
// Album name from last breadcrumb
|
||||||
|
albumCrumb := breadcrumbElements.Eq(3)
|
||||||
|
album.Name = strings.TrimSpace(albumCrumb.Text())
|
||||||
|
|
||||||
|
// Artist from second last breadcrumb
|
||||||
|
artistCrumb := breadcrumbElements.Eq(2)
|
||||||
|
artistAnchor := artistCrumb.Find("a")
|
||||||
|
if artistAnchor.Length() > 0 {
|
||||||
|
if href, exists := artistAnchor.Attr("href"); exists {
|
||||||
|
hrefParts := strings.Split(href, "/")
|
||||||
|
if len(hrefParts) > 1 && hrefParts[len(hrefParts)-2] == "artist" {
|
||||||
|
artist := data.Artist{}
|
||||||
|
|
||||||
|
artist.Sources = append(artist.Sources, data.Source{
|
||||||
|
Url: musifyHost + strings.TrimSpace(href),
|
||||||
|
ObjectType: data.ArtistSource,
|
||||||
|
SourceType: &m.sourceType,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Artist name from span
|
||||||
|
if span := artistAnchor.Find("span"); span.Length() > 0 {
|
||||||
|
artist.Name = strings.TrimSpace(span.Text())
|
||||||
|
} else {
|
||||||
|
artist.Name = strings.TrimSpace(artistAnchor.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
album.Artists = append(album.Artists, artist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// m.logger.Debug("there are not 4 breadcrumb items, which shouldn't be the case")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta tags
|
||||||
|
metaURL := doc.Find("meta[itemprop='url']")
|
||||||
|
if metaURL.Length() > 0 {
|
||||||
|
if content, exists := metaURL.Attr("content"); exists {
|
||||||
|
album.Sources = append(album.Sources, data.Source{
|
||||||
|
Url: musifyHost + content,
|
||||||
|
ObjectType: data.AlbumSource,
|
||||||
|
SourceType: &m.sourceType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metaName := doc.Find("meta[itemprop='name']")
|
||||||
|
if metaName.Length() > 0 {
|
||||||
|
if content, exists := metaName.Attr("content"); exists {
|
||||||
|
album.Name = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Album info
|
||||||
|
albumInfo := doc.Find("ul.album-info")
|
||||||
|
if albumInfo.Length() > 0 {
|
||||||
|
// Artists
|
||||||
|
albumInfo.Find("a[itemprop='byArtist']").Each(func(i int, artistAnchor *goquery.Selection) {
|
||||||
|
artist := data.Artist{
|
||||||
|
Sources: []data.Source{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artist URL
|
||||||
|
artistURLMeta := artistAnchor.Find("meta[itemprop='url']")
|
||||||
|
if artistURLMeta.Length() > 0 {
|
||||||
|
if content, exists := artistURLMeta.Attr("content"); exists {
|
||||||
|
artist.Sources = append(artist.Sources, data.Source{
|
||||||
|
Url: musifyHost + content,
|
||||||
|
ObjectType: data.ArtistSource,
|
||||||
|
SourceType: &m.sourceType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artist name
|
||||||
|
artistNameMeta := artistAnchor.Find("meta[itemprop='name']")
|
||||||
|
if artistNameMeta.Length() > 0 {
|
||||||
|
if content, exists := artistNameMeta.Attr("content"); exists {
|
||||||
|
artist.Name = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if artist.Name != "" {
|
||||||
|
album.Artists = append(album.Artists, artist)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Date published
|
||||||
|
timeSelection := albumInfo.Find("time[itemprop='datePublished']")
|
||||||
|
if timeSelection.Length() > 0 {
|
||||||
|
if datetime, exists := timeSelection.Attr("datetime"); exists {
|
||||||
|
// Note: You'll need to parse the datetime according to your needs
|
||||||
|
// For now, we'll store it as a string or you can parse it to time.Time
|
||||||
|
// album.Date = parsedDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// Album artwork would be handled here based on your ArtworkCollection implementation
|
||||||
|
|
||||||
|
return album
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Musify) FetchAlbum(source data.Source) (data.Album, error) {
|
func (m Musify) FetchAlbum(source data.Source) (data.Album, error) {
|
||||||
return data.Album{
|
album := data.Album{
|
||||||
Name: extractName(source.Url),
|
Sources: []data.Source{source},
|
||||||
}, nil
|
Artists: []data.Artist{},
|
||||||
|
Songs: []data.Song{},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := m.session.Get(source.Url)
|
||||||
|
if err != nil {
|
||||||
|
return album, err
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := scraper.GetHtml(resp)
|
||||||
|
if err != nil {
|
||||||
|
return album, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse album metadata
|
||||||
|
parsedAlbum := m.parseAlbum(doc)
|
||||||
|
album.Name = parsedAlbum.Name
|
||||||
|
album.Artists = parsedAlbum.Artists
|
||||||
|
album.Sources = append(album.Sources, parsedAlbum.Sources...)
|
||||||
|
|
||||||
|
// Parse songs from cards
|
||||||
|
cardBody := doc.Find("div.card-body")
|
||||||
|
if cardBody.Length() > 0 {
|
||||||
|
cardBody.Find("div.playlist__item").Each(func(i int, songCard *goquery.Selection) {
|
||||||
|
song := m.parseSongCard(songCard)
|
||||||
|
album.Songs = append(album.Songs, song)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tracksort would be handled here based on your implementation
|
||||||
|
|
||||||
|
return album, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Musify) FetchArtist(source data.Source) (data.Artist, error) {
|
type parsedArtistUrl struct {
|
||||||
return data.Artist{
|
id string
|
||||||
Name: extractName(source.Url),
|
name string
|
||||||
}, nil
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParsedArtistUrl(rawUrl string) (parsedArtistUrl, error) {
|
||||||
|
res := parsedArtistUrl{
|
||||||
|
url: rawUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := url.Parse(rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs := strings.Split(parsed.Path, "/")
|
||||||
|
correctPart := dirs[len(dirs)-1]
|
||||||
|
split := strings.Split(correctPart, "-")
|
||||||
|
if len(split) < 2 {
|
||||||
|
return res, errors.New("last part of path has to consist of at least one - " + correctPart)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.id = split[len(split)-1]
|
||||||
|
res.name = strings.Join(split[:len(split)-1], "-")
|
||||||
|
|
||||||
|
if !common.IsNumeric(res.id) {
|
||||||
|
return res, errors.New("last elem (id) has to be numeric " + res.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Musify) fetchInitialArtist(parsed parsedArtistUrl, artist data.Artist) (data.Artist, error) {
|
||||||
|
endpoint := fmt.Sprintf("https://musify.club/artist/%s?_pjax=#bodyContent", parsed.name)
|
||||||
|
resp, err := m.session.Get(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return artist, err
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := scraper.GetHtml(resp)
|
||||||
|
if err != nil {
|
||||||
|
return artist, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breadcrumbs
|
||||||
|
breadcrumbs := doc.Find("ol.breadcrumb")
|
||||||
|
if breadcrumbs.Length() > 0 {
|
||||||
|
breadcrumbList := breadcrumbs.Find("li.breadcrumb-item")
|
||||||
|
if breadcrumbList.Length() == 3 {
|
||||||
|
artist.Name = strings.TrimSpace(breadcrumbList.Eq(2).Text())
|
||||||
|
} else {
|
||||||
|
return artist, errors.New("breadcrumb layout on artist page changed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nav tabs for songs link
|
||||||
|
navTabs := doc.Find("ul.nav-tabs")
|
||||||
|
if navTabs.Length() > 0 {
|
||||||
|
navTabs.Find("li.nav-item").Each(func(i int, listItem *goquery.Selection) {
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(listItem.Text()), "песни") {
|
||||||
|
// "песни" translates to "songs"
|
||||||
|
anchor := listItem.Find("a")
|
||||||
|
if href, exists := anchor.Attr("href"); exists {
|
||||||
|
artist.Sources = append(artist.Sources, data.Source{
|
||||||
|
Url: musifyHost + href,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content title
|
||||||
|
contentTitle := doc.Find("header.content__title")
|
||||||
|
if contentTitle.Length() > 0 {
|
||||||
|
h1Name := contentTitle.Find("h1")
|
||||||
|
if h1Name.Length() > 0 {
|
||||||
|
artist.Name = strings.TrimSpace(h1Name.Text())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Country and additional sources from icon list
|
||||||
|
iconList := doc.Find("ul.icon-list")
|
||||||
|
if iconList.Length() > 0 {
|
||||||
|
// Country flag - simplified version
|
||||||
|
countryFlag := iconList.Find("i.flag-icon")
|
||||||
|
if countryFlag.Length() > 0 {
|
||||||
|
// Extract country code from class names
|
||||||
|
classes, _ := countryFlag.Attr("class")
|
||||||
|
classList := strings.Fields(classes)
|
||||||
|
for _, class := range classList {
|
||||||
|
if class != "flag-icon" && class != "shadow" && len(class) == 2 {
|
||||||
|
// This would be where you'd use a country lookup library
|
||||||
|
// artist.Country = getCountryFromCode(class)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional sources
|
||||||
|
iconList.Find("a.link").Each(func(i int, additionalSource *goquery.Selection) {
|
||||||
|
if href, exists := additionalSource.Attr("href"); exists {
|
||||||
|
// Simplified source matching - you'd implement your Source.matchUrl equivalent
|
||||||
|
artist.Sources = append(artist.Sources, data.Source{Url: href})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Notes
|
||||||
|
noteSection := doc.Find("#text-main")
|
||||||
|
if noteSection.Length() > 0 {
|
||||||
|
html, _ := noteSection.Html()
|
||||||
|
// artist.Notes = FormattedText{HTML: html} - if you have this field
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artist artwork
|
||||||
|
doc.Find("img.artist-img").Each(func(i int, img *goquery.Selection) {
|
||||||
|
src, exists := img.Attr("data-src")
|
||||||
|
if !exists {
|
||||||
|
src, _ = img.Attr("src")
|
||||||
|
}
|
||||||
|
if src != "" {
|
||||||
|
// artist.Artwork = append(artist.Artwork, Artwork{Url: src})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
return artist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Musify) parseAlbumCard(albumCard *goquery.Selection, artistName string) (data.Album, error) {
|
||||||
|
album := data.Album{
|
||||||
|
Sources: []data.Source{},
|
||||||
|
Artists: []data.Artist{},
|
||||||
|
Songs: []data.Song{},
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Album type from data attribute
|
||||||
|
if albumTypeID, exists := albumCard.Attr("data-type"); exists {
|
||||||
|
if parsedType, err := strconv.Atoi(albumTypeID); err == nil {
|
||||||
|
// album.Type = getAlbumTypeFromID(parsedType) - implement your mapping
|
||||||
|
if parsedType == 5 {
|
||||||
|
// album.Status = AlbumStatusBootleg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Parse release anchor
|
||||||
|
parseReleaseAnchor := func(anchor *goquery.Selection, textIsName bool) {
|
||||||
|
if anchor == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if href, exists := anchor.Attr("href"); exists {
|
||||||
|
album.Sources = append(album.Sources, data.Source{
|
||||||
|
Url: musifyHost + href,
|
||||||
|
ObjectType: data.AlbumSource,
|
||||||
|
SourceType: &m.sourceType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if textIsName {
|
||||||
|
album.Name = common.CleanSongTitle(strings.TrimSpace(anchor.Text()), artistName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main thumbnail anchor
|
||||||
|
anchorList := albumCard.Find("a")
|
||||||
|
if anchorList.Length() > 0 {
|
||||||
|
mainAnchor := anchorList.First()
|
||||||
|
parseReleaseAnchor(mainAnchor, false)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Thumbnail image
|
||||||
|
thumbnail := mainAnchor.Find("img")
|
||||||
|
if thumbnail.Length() > 0 {
|
||||||
|
if alt, exists := thumbnail.Attr("alt"); exists {
|
||||||
|
album.Name = common.CleanSongTitle(alt, artistName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image URL could be stored if needed
|
||||||
|
// if src, exists := thumbnail.Attr("src"); exists { ... }
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
return album, errors.New("the card has no thumbnail or url")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card body
|
||||||
|
cardBody := albumCard.Find("div.card-body")
|
||||||
|
if cardBody.Length() > 0 {
|
||||||
|
parseReleaseAnchor(cardBody.Find("a"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Parse date from card footer
|
||||||
|
parseSmallDate := func(small *goquery.Selection) {
|
||||||
|
italic := small.Find("i")
|
||||||
|
if italic.Length() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if title, exists := italic.Attr("title"); exists && title == "Добавлено" {
|
||||||
|
rawTime := strings.TrimSpace(small.Text())
|
||||||
|
// Parse date from "13.11.2021" format
|
||||||
|
// album.Date = parseDate(rawTime, "02.01.2006")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card footers
|
||||||
|
cardFooters := albumCard.Find("div.card-footer")
|
||||||
|
if cardFooters.Length() == 3 {
|
||||||
|
lastFooter := cardFooters.Last()
|
||||||
|
lastFooter.Find("small").Each(func(i int, small *goquery.Selection) {
|
||||||
|
parseSmallDate(small)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
m.logger.Debug(fmt.Sprintf("expected 3 card footers, got %d", cardFooters.Length()))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return album, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Musify) fetchArtistDiscography(url parsedArtistUrl, artistName string, albumTypeBlacklist []string) ([]data.Album, error) {
|
||||||
|
albumList := []data.Album{}
|
||||||
|
|
||||||
|
endpoint := "https://musify.club/discography/filteralbums"
|
||||||
|
|
||||||
|
// POST request with form data
|
||||||
|
formData := map[string]string{
|
||||||
|
"ArtistID": url.id,
|
||||||
|
"SortOrder.Property": "dateCreated",
|
||||||
|
"SortOrder.IsAscending": "false",
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
}
|
||||||
|
resp, err := m.session.PostForm(endpoint, formData, map[string]string{"X-Requested-With": "XMLHttpRequest"})
|
||||||
|
if err != nil {
|
||||||
|
return albumList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := scraper.GetHtml(resp)
|
||||||
|
if err != nil {
|
||||||
|
return albumList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Find("div.card").Each(func(i int, card *goquery.Selection) {
|
||||||
|
album, err := m.parseAlbumCard(card, artistName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
albumList = append(albumList, album)
|
||||||
|
})
|
||||||
|
|
||||||
|
return albumList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Musify) FetchArtist(source data.Source) (data.Artist, error) {
|
||||||
|
res := data.Artist{
|
||||||
|
Name: extractName(source.Url),
|
||||||
|
}
|
||||||
|
parsed, err := newParsedArtistUrl(source.Url)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = m.fetchInitialArtist(parsed, res)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
albumList, err := m.fetchArtistDiscography(parsed, res.Name, []string{})
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.Albums = append(res.Albums, albumList...)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (m MusifyTest) RegexSong() *regexp.Regexp {
|
|||||||
return regexp.MustCompile(`(?i)https?://musify\.club/track/[a-z\-0-9]+`)
|
return regexp.MustCompile(`(?i)https?://musify\.club/track/[a-z\-0-9]+`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MusifyTest) Init() {
|
func (m *MusifyTest) Init(sourceType data.SourceType) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ func TestRegister(t *testing.T) {
|
|||||||
func TestFetchSong(t *testing.T) {
|
func TestFetchSong(t *testing.T) {
|
||||||
RegisterPlugin(&MusifyTest{})
|
RegisterPlugin(&MusifyTest{})
|
||||||
|
|
||||||
s, err := Fetch(data.Source{
|
s, err := FetchSource(data.Source{
|
||||||
Url: "https://musify.club/track/linkin-park-in-the-end-3058",
|
Url: "https://musify.club/track/linkin-park-in-the-end-3058",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ func TestFetchSong(t *testing.T) {
|
|||||||
func TestFetchAlbum(t *testing.T) {
|
func TestFetchAlbum(t *testing.T) {
|
||||||
RegisterPlugin(&MusifyTest{})
|
RegisterPlugin(&MusifyTest{})
|
||||||
|
|
||||||
a, err := Fetch(data.Source{
|
a, err := FetchSource(data.Source{
|
||||||
Url: "https://musify.club/release/linkin-park-hybrid-theory-2000-188",
|
Url: "https://musify.club/release/linkin-park-hybrid-theory-2000-188",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ func TestFetchAlbum(t *testing.T) {
|
|||||||
func TestFetchArtist(t *testing.T) {
|
func TestFetchArtist(t *testing.T) {
|
||||||
RegisterPlugin(&MusifyTest{})
|
RegisterPlugin(&MusifyTest{})
|
||||||
|
|
||||||
a, err := Fetch(data.Source{
|
a, err := FetchSource(data.Source{
|
||||||
Url: "https://musify.club/artist/linkin-park-5",
|
Url: "https://musify.club/artist/linkin-park-5",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ func TestFetchArtist(t *testing.T) {
|
|||||||
func TestFetchWrongUrl(t *testing.T) {
|
func TestFetchWrongUrl(t *testing.T) {
|
||||||
RegisterPlugin(&MusifyTest{})
|
RegisterPlugin(&MusifyTest{})
|
||||||
|
|
||||||
_, err := Fetch(data.Source{
|
_, err := FetchSource(data.Source{
|
||||||
Url: "https://musify.club/",
|
Url: "https://musify.club/",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ func TestFetchWrongUrl(t *testing.T) {
|
|||||||
func TestNonExistentSourceType(t *testing.T) {
|
func TestNonExistentSourceType(t *testing.T) {
|
||||||
RegisterPlugin(&MusifyTest{})
|
RegisterPlugin(&MusifyTest{})
|
||||||
|
|
||||||
_, err := Fetch(data.Source{
|
_, err := FetchSource(data.Source{
|
||||||
Url: "https://musify.club/",
|
Url: "https://musify.club/",
|
||||||
SourceType: &data.SourceType{
|
SourceType: &data.SourceType{
|
||||||
Name: "doesn't exist",
|
Name: "doesn't exist",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
@@ -118,6 +119,32 @@ func (s *Session) PostMultipartForm(url string, data map[string]string, headers
|
|||||||
return s.client.Do(req)
|
return s.client.Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Session) PostForm(rawUrl string, data map[string]string, headers ...map[string]string) (*http.Response, error) {
|
||||||
|
fullURL := s.buildURL(rawUrl)
|
||||||
|
|
||||||
|
// Prepare form data
|
||||||
|
formData := url.Values{}
|
||||||
|
for k, v := range data {
|
||||||
|
formData.Add(k, v)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", fullURL, strings.NewReader(formData.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.setDefaultHeaders(req)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
// Add any additional headers provided
|
||||||
|
if len(headers) > 0 {
|
||||||
|
for key, value := range headers[0] {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
// PostJSON performs a POST request with JSON data
|
// PostJSON performs a POST request with JSON data
|
||||||
func (s *Session) PostJSON(url string, data interface{}, headers ...map[string]string) (*http.Response, error) {
|
func (s *Session) PostJSON(url string, data interface{}, headers ...map[string]string) (*http.Response, error) {
|
||||||
fullURL := s.buildURL(url)
|
fullURL := s.buildURL(url)
|
||||||
|
|||||||
Reference in New Issue
Block a user