Compare commits
No commits in common. "4a54048febd921da95022489f0a8e6cf9ff409c3" and "183b03ee64490e8e3eba5c12e46c06f0c5d42ae2" have entirely different histories.
4a54048feb
...
183b03ee64
2
go.mod
2
go.mod
@ -1,5 +1,3 @@
|
|||||||
module gitea.elara.ws/Hazel/music-kraken
|
module gitea.elara.ws/Hazel/music-kraken
|
||||||
|
|
||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require golang.org/x/net v0.45.0 // indirect
|
|
||||||
|
2
go.sum
2
go.sum
@ -1,2 +0,0 @@
|
|||||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
|
||||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
|
@ -1,47 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gitea.elara.ws/Hazel/music-kraken/internal/data"
|
|
||||||
"gitea.elara.ws/Hazel/music-kraken/internal/plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func printResults(musicObjects []data.MusicObject) {
|
|
||||||
for _, m := range musicObjects {
|
|
||||||
if a, ok := m.(data.Artist); ok {
|
|
||||||
fmt.Println("artist: " + a.Name)
|
|
||||||
} else if a, ok := m.(data.Album); ok {
|
|
||||||
fmt.Println("release: " + a.Name)
|
|
||||||
} else if a, ok := m.(data.Song); ok {
|
|
||||||
fmt.Println("track: " + a.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Shell() {
|
|
||||||
plugin.RegisterPlugin(&plugin.Musify{})
|
|
||||||
|
|
||||||
for {
|
|
||||||
fmt.Print("> ")
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
line, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
searchResults, err := plugin.Search(line, plugin.SearchConfig{IgnoreErrors: false})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
printResults(searchResults)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,7 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -120,26 +116,3 @@ func NewQuery(search string) (Query, error) {
|
|||||||
|
|
||||||
return query, nil
|
return query, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryParsing() {
|
|
||||||
for {
|
|
||||||
fmt.Print("> ")
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
line, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
query, err := NewQuery(line)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
fmt.Println("search: '" + query.Search + "'")
|
|
||||||
fmt.Println("artist: '" + query.Artist + "'")
|
|
||||||
fmt.Println("album: '" + query.Album + "'")
|
|
||||||
fmt.Println("song: '" + query.Song + "'")
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -15,8 +15,6 @@ type Plugin interface {
|
|||||||
RegexAlbum() *regexp.Regexp
|
RegexAlbum() *regexp.Regexp
|
||||||
RegexSong() *regexp.Regexp
|
RegexSong() *regexp.Regexp
|
||||||
|
|
||||||
Init()
|
|
||||||
|
|
||||||
Search(query common.Query) ([]data.MusicObject, error)
|
Search(query common.Query) ([]data.MusicObject, error)
|
||||||
|
|
||||||
FetchArtist(source data.Source) (data.Artist, error)
|
FetchArtist(source data.Source) (data.Artist, error)
|
||||||
@ -43,9 +41,6 @@ func RegisterPlugin(plugin Plugin) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namePlugins[name] = plugin
|
namePlugins[name] = plugin
|
||||||
|
|
||||||
plugin.Init()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.elara.ws/Hazel/music-kraken/internal/common"
|
"gitea.elara.ws/Hazel/music-kraken/internal/common"
|
||||||
"gitea.elara.ws/Hazel/music-kraken/internal/data"
|
"gitea.elara.ws/Hazel/music-kraken/internal/data"
|
||||||
"gitea.elara.ws/Hazel/music-kraken/internal/scraper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func extractName(s string) string {
|
func extractName(s string) string {
|
||||||
@ -21,7 +17,6 @@ func extractName(s string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Musify struct {
|
type Musify struct {
|
||||||
session *scraper.Session
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Musify) Name() string {
|
func (m Musify) Name() string {
|
||||||
@ -40,54 +35,10 @@ 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() {
|
|
||||||
m.session = scraper.NewSession()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (m *Musify) Search(query common.Query) ([]data.MusicObject, error) {
|
|
||||||
musicObjects := []data.MusicObject{}
|
|
||||||
|
|
||||||
resp, err := m.session.PostMultipartForm("https://musify.club/en/search", map[string]string{
|
|
||||||
"SearchText": query.Search, // alternatively I could also add year and genre
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return musicObjects, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var bodyReader io.Reader = resp.Body
|
|
||||||
|
|
||||||
// Check if we need to decompress manually
|
|
||||||
if resp.Header.Get("Content-Encoding") == "gzip" && false {
|
|
||||||
fmt.Println("Response is gzipped, decompressing...")
|
|
||||||
gzReader, err := gzip.NewReader(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer gzReader.Close()
|
|
||||||
bodyReader = gzReader
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(bodyReader)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Response length: %d bytes\n", len(body))
|
|
||||||
fmt.Println("Content:")
|
|
||||||
fmt.Println(string(body))
|
|
||||||
|
|
||||||
fmt.Println(resp.Header)
|
|
||||||
fmt.Println(resp.StatusCode)
|
|
||||||
|
|
||||||
return musicObjects, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Musify) FetchSong(source data.Source) (data.Song, error) {
|
func (m Musify) FetchSong(source data.Source) (data.Song, error) {
|
||||||
return data.Song{
|
return data.Song{
|
||||||
Name: extractName(source.Url),
|
Name: extractName(source.Url),
|
||||||
@ -105,3 +56,7 @@ func (m Musify) FetchArtist(source data.Source) (data.Artist, error) {
|
|||||||
Name: extractName(source.Url),
|
Name: extractName(source.Url),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Musify) Search(query common.Query) ([]data.MusicObject, error) {
|
||||||
|
return []data.MusicObject{}, nil
|
||||||
|
}
|
||||||
|
@ -40,10 +40,6 @@ 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) FetchSong(source data.Source) (data.Song, error) {
|
func (m MusifyTest) FetchSong(source data.Source) (data.Song, error) {
|
||||||
return data.Song{
|
return data.Song{
|
||||||
Name: extractNameTest(source.Url),
|
Name: extractNameTest(source.Url),
|
||||||
@ -67,11 +63,11 @@ func (m MusifyTest) Search(query common.Query) ([]data.MusicObject, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRegister(t *testing.T) {
|
func TestRegister(t *testing.T) {
|
||||||
if RegisterPlugin(&MusifyTest{}) != nil {
|
if RegisterPlugin(MusifyTest{}) != nil {
|
||||||
t.Errorf(`registering first plugin shouldn't return an error`)
|
t.Errorf(`registering first plugin shouldn't return an error`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if RegisterPlugin(&MusifyTest{}) == nil {
|
if RegisterPlugin(MusifyTest{}) == nil {
|
||||||
t.Errorf(`registering same plugin twice should return an error`)
|
t.Errorf(`registering same plugin twice should return an error`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +81,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 := Fetch(data.Source{
|
||||||
Url: "https://musify.club/track/linkin-park-in-the-end-3058",
|
Url: "https://musify.club/track/linkin-park-in-the-end-3058",
|
||||||
@ -106,7 +102,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 := Fetch(data.Source{
|
||||||
Url: "https://musify.club/release/linkin-park-hybrid-theory-2000-188",
|
Url: "https://musify.club/release/linkin-park-hybrid-theory-2000-188",
|
||||||
@ -127,7 +123,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 := Fetch(data.Source{
|
||||||
Url: "https://musify.club/artist/linkin-park-5",
|
Url: "https://musify.club/artist/linkin-park-5",
|
||||||
@ -148,7 +144,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 := Fetch(data.Source{
|
||||||
Url: "https://musify.club/",
|
Url: "https://musify.club/",
|
||||||
@ -160,7 +156,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 := Fetch(data.Source{
|
||||||
Url: "https://musify.club/",
|
Url: "https://musify.club/",
|
||||||
|
@ -1,185 +0,0 @@
|
|||||||
package scraper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"log"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Session represents a persistent HTTP session
|
|
||||||
type Session struct {
|
|
||||||
client *http.Client
|
|
||||||
headers map[string]string
|
|
||||||
baseURL string
|
|
||||||
UserAgent string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSession creates a new session with browser-like headers
|
|
||||||
func NewSession() *Session {
|
|
||||||
// Create cookie jar first
|
|
||||||
jar, err := cookiejar.New(&cookiejar.Options{
|
|
||||||
PublicSuffixList: publicsuffix.List,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Session{
|
|
||||||
client: &http.Client{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
Jar: jar, // Set the cookie jar
|
|
||||||
},
|
|
||||||
headers: map[string]string{
|
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
|
||||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
||||||
"Accept-Language": "en-US,en;q=0.5",
|
|
||||||
"Connection": "keep-alive",
|
|
||||||
"Upgrade-Insecure-Requests": "1",
|
|
||||||
},
|
|
||||||
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeader sets a header for all subsequent requests
|
|
||||||
func (s *Session) SetHeader(key, value string) {
|
|
||||||
s.headers[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeaders sets multiple headers at once
|
|
||||||
func (s *Session) SetHeaders(headers map[string]string) {
|
|
||||||
for key, value := range headers {
|
|
||||||
s.headers[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBaseURL sets the base URL for relative paths
|
|
||||||
func (s *Session) SetBaseURL(baseURL string) {
|
|
||||||
s.baseURL = baseURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get performs a GET request
|
|
||||||
func (s *Session) Get(url string, headers ...map[string]string) (*http.Response, error) {
|
|
||||||
// Use base URL if set and url is relative
|
|
||||||
fullURL := s.buildURL(url)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", fullURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.setDefaultHeaders(req)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post performs a POST request with form data
|
|
||||||
func (s *Session) PostMultipartForm(url string, data map[string]string, headers ...map[string]string) (*http.Response, error) {
|
|
||||||
fullURL := s.buildURL(url)
|
|
||||||
|
|
||||||
var requestBody bytes.Buffer
|
|
||||||
writer := multipart.NewWriter(&requestBody)
|
|
||||||
for k, v := range data {
|
|
||||||
err := writer.WriteField(k, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writer.Close()
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", fullURL, &requestBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.setDefaultHeaders(req)
|
|
||||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
||||||
|
|
||||||
// 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
|
|
||||||
func (s *Session) PostJSON(url string, data interface{}, headers ...map[string]string) (*http.Response, error) {
|
|
||||||
fullURL := s.buildURL(url)
|
|
||||||
|
|
||||||
jsonData, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", fullURL, bytes.NewBuffer(jsonData))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.setDefaultHeaders(req)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildURL constructs the full URL using baseURL if set
|
|
||||||
func (s *Session) buildURL(path string) string {
|
|
||||||
if s.baseURL != "" && !isAbsoluteURL(path) {
|
|
||||||
return s.baseURL + path
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// isAbsoluteURL checks if the URL is absolute
|
|
||||||
func isAbsoluteURL(urlStr string) bool {
|
|
||||||
u, err := url.Parse(urlStr)
|
|
||||||
return err == nil && u.Scheme != "" && u.Host != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDefaultHeaders sets the default browser-like headers
|
|
||||||
func (s *Session) setDefaultHeaders(req *http.Request) {
|
|
||||||
for key, value := range s.headers {
|
|
||||||
req.Header.Set(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCookies returns cookies for a given URL
|
|
||||||
func (s *Session) GetCookies(urlStr string) []*http.Cookie {
|
|
||||||
u, err := url.Parse(urlStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.client.Jar.Cookies(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCookies sets cookies for a given URL
|
|
||||||
func (s *Session) SetCookies(urlStr string, cookies []*http.Cookie) {
|
|
||||||
u, err := url.Parse(urlStr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.client.Jar.SetCookies(u, cookies)
|
|
||||||
}
|
|
34
main.go
34
main.go
@ -1,13 +1,39 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
"gitea.elara.ws/Hazel/music-kraken/internal/cli"
|
"gitea.elara.ws/Hazel/music-kraken/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func testQuery() {
|
||||||
fmt.Println("welcome to music kraken")
|
for {
|
||||||
|
fmt.Print("> ")
|
||||||
|
|
||||||
cli.Shell()
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query, err := common.NewQuery(line)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
fmt.Println("search: '" + query.Search + "'")
|
||||||
|
fmt.Println("artist: '" + query.Artist + "'")
|
||||||
|
fmt.Println("album: '" + query.Album + "'")
|
||||||
|
fmt.Println("song: '" + query.Song + "'")
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("music kraken")
|
||||||
|
|
||||||
|
testQuery()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user