From 7246b5f0bc38c38dafd5683eadafee7c15540286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C4=99pie=C5=84?= Date: Thu, 19 Dec 2024 19:21:06 +0100 Subject: [PATCH 1/4] Restructure project --- cmd/feelbeatserver/main.go | 10 +++++----- cmd/feelbeatserver/setupconfig.go | 4 ++-- internal/{ => infra}/fblog/main.go | 2 +- internal/{networking => infra/ws}/client.go | 6 +++--- internal/{networking => infra/ws}/hub.go | 6 +++--- internal/{networking => infra/ws}/message.go | 2 +- internal/{networking => infra/ws}/servewebsockets.go | 6 +++--- internal/{networking => infra/ws}/types.go | 2 +- .../{networking_test => infra/ws_test}/hub_test.go | 11 ++++------- internal/{ => lib}/component/main.go | 0 10 files changed, 23 insertions(+), 26 deletions(-) rename internal/{ => infra}/fblog/main.go (93%) rename internal/{networking => infra/ws}/client.go (94%) rename internal/{networking => infra/ws}/hub.go (90%) rename internal/{networking => infra/ws}/message.go (77%) rename internal/{networking => infra/ws}/servewebsockets.go (81%) rename internal/{networking => infra/ws}/types.go (93%) rename internal/{networking_test => infra/ws_test}/hub_test.go (80%) rename internal/{ => lib}/component/main.go (100%) diff --git a/cmd/feelbeatserver/main.go b/cmd/feelbeatserver/main.go index 3daba9f..25eabf2 100644 --- a/cmd/feelbeatserver/main.go +++ b/cmd/feelbeatserver/main.go @@ -6,9 +6,9 @@ import ( "net/http" "os" - "github.com/feelbeatapp/feelbeatserver/internal/component" - "github.com/feelbeatapp/feelbeatserver/internal/fblog" - "github.com/feelbeatapp/feelbeatserver/internal/networking" + "github.com/feelbeatapp/feelbeatserver/internal/lib/component" + "github.com/feelbeatapp/feelbeatserver/internal/infra/fblog" + "github.com/feelbeatapp/feelbeatserver/internal/infra/ws" "github.com/knadh/koanf/v2" ) @@ -32,11 +32,11 @@ func main() { port := config.MustInt("websocket.port") path := config.MustString("websocket.path") - hub := networking.NewHub() + hub := ws.NewHub() go hub.Run() http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - networking.ServeWebsockets(hub, w, r) + ws.ServeWebsockets(hub, w, r) }) fblog.Info(component.FeelBeatServer, "Server started", "port", port) diff --git a/cmd/feelbeatserver/setupconfig.go b/cmd/feelbeatserver/setupconfig.go index f683363..85b48d8 100644 --- a/cmd/feelbeatserver/setupconfig.go +++ b/cmd/feelbeatserver/setupconfig.go @@ -5,8 +5,8 @@ import ( "io/fs" "strings" - "github.com/feelbeatapp/feelbeatserver/internal/component" - "github.com/feelbeatapp/feelbeatserver/internal/fblog" + "github.com/feelbeatapp/feelbeatserver/internal/lib/component" + "github.com/feelbeatapp/feelbeatserver/internal/infra/fblog" "github.com/knadh/koanf/parsers/toml" "github.com/knadh/koanf/providers/env" "github.com/knadh/koanf/providers/file" diff --git a/internal/fblog/main.go b/internal/infra/fblog/main.go similarity index 93% rename from internal/fblog/main.go rename to internal/infra/fblog/main.go index 58232bc..39fe35f 100644 --- a/internal/fblog/main.go +++ b/internal/infra/fblog/main.go @@ -6,7 +6,7 @@ import ( "os" "time" - "github.com/feelbeatapp/feelbeatserver/internal/component" + "github.com/feelbeatapp/feelbeatserver/internal/lib/component" "github.com/lmittmann/tint" ) diff --git a/internal/networking/client.go b/internal/infra/ws/client.go similarity index 94% rename from internal/networking/client.go rename to internal/infra/ws/client.go index 51c7424..ac4a7bb 100644 --- a/internal/networking/client.go +++ b/internal/infra/ws/client.go @@ -1,10 +1,10 @@ -package networking +package ws import ( "time" - "github.com/feelbeatapp/feelbeatserver/internal/component" - "github.com/feelbeatapp/feelbeatserver/internal/fblog" + "github.com/feelbeatapp/feelbeatserver/internal/lib/component" + "github.com/feelbeatapp/feelbeatserver/internal/infra/fblog" "github.com/gorilla/websocket" ) diff --git a/internal/networking/hub.go b/internal/infra/ws/hub.go similarity index 90% rename from internal/networking/hub.go rename to internal/infra/ws/hub.go index 982acee..2ebe45a 100644 --- a/internal/networking/hub.go +++ b/internal/infra/ws/hub.go @@ -1,8 +1,8 @@ -package networking +package ws import ( - "github.com/feelbeatapp/feelbeatserver/internal/component" - "github.com/feelbeatapp/feelbeatserver/internal/fblog" + "github.com/feelbeatapp/feelbeatserver/internal/lib/component" + "github.com/feelbeatapp/feelbeatserver/internal/infra/fblog" ) type BasicHub struct { diff --git a/internal/networking/message.go b/internal/infra/ws/message.go similarity index 77% rename from internal/networking/message.go rename to internal/infra/ws/message.go index 1dc2ae4..d350137 100644 --- a/internal/networking/message.go +++ b/internal/infra/ws/message.go @@ -1,4 +1,4 @@ -package networking +package ws type ClientMessage struct { From HubClient diff --git a/internal/networking/servewebsockets.go b/internal/infra/ws/servewebsockets.go similarity index 81% rename from internal/networking/servewebsockets.go rename to internal/infra/ws/servewebsockets.go index fd434c1..06c7dd1 100644 --- a/internal/networking/servewebsockets.go +++ b/internal/infra/ws/servewebsockets.go @@ -1,11 +1,11 @@ -package networking +package ws import ( "net/http" "os" - "github.com/feelbeatapp/feelbeatserver/internal/component" - "github.com/feelbeatapp/feelbeatserver/internal/fblog" + "github.com/feelbeatapp/feelbeatserver/internal/lib/component" + "github.com/feelbeatapp/feelbeatserver/internal/infra/fblog" "github.com/gorilla/websocket" ) diff --git a/internal/networking/types.go b/internal/infra/ws/types.go similarity index 93% rename from internal/networking/types.go rename to internal/infra/ws/types.go index d5ce658..58467c1 100644 --- a/internal/networking/types.go +++ b/internal/infra/ws/types.go @@ -1,4 +1,4 @@ -package networking +package ws type Hub interface { RegisterClient(HubClient) diff --git a/internal/networking_test/hub_test.go b/internal/infra/ws_test/hub_test.go similarity index 80% rename from internal/networking_test/hub_test.go rename to internal/infra/ws_test/hub_test.go index 82ba0bf..8a93a28 100644 --- a/internal/networking_test/hub_test.go +++ b/internal/infra/ws_test/hub_test.go @@ -1,11 +1,10 @@ -package networking_test +package ws_test import ( - "fmt" "testing" "time" - "github.com/feelbeatapp/feelbeatserver/internal/networking" + "github.com/feelbeatapp/feelbeatserver/internal/infra/ws" "github.com/stretchr/testify/assert" ) @@ -22,9 +21,7 @@ func newFakeClient() *FakeClient { } func (c *FakeClient) Send(payload []byte) { - fmt.Println("Let's go I'm really sending sht") c.payloads = append(c.payloads, payload) - fmt.Println(c.payloads) } func (c *FakeClient) CloseNow() { @@ -39,7 +36,7 @@ const testMessage = "hi there" func TestHubBroadcastsMessages(t *testing.T) { assert := assert.New(t) - hub := networking.NewHub() + hub := ws.NewHub() go hub.Run() @@ -49,7 +46,7 @@ func TestHubBroadcastsMessages(t *testing.T) { hub.RegisterClient(clients[i]) } - hub.Broadcast(networking.ClientMessage{ + hub.Broadcast(ws.ClientMessage{ From: clients[0], Payload: []byte(testMessage), }) diff --git a/internal/component/main.go b/internal/lib/component/main.go similarity index 100% rename from internal/component/main.go rename to internal/lib/component/main.go From bc41f9bf7e9d9a12e759ae103ec65bed8147e0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C4=99pie=C5=84?= Date: Thu, 19 Dec 2024 20:58:05 +0100 Subject: [PATCH 2/4] Prototype youtube searching --- cmd/feelbeatserver/main.go | 4 +- cmd/feelbeatserver/setupconfig.go | 6 +- cmd/yttests/main.go | 19 +++ go.mod | 1 + go.sum | 2 + internal/thirdparty/ytsearch/searchresult.go | 9 ++ internal/thirdparty/ytsearch/ytsearch.go | 121 +++++++++++++++++++ 7 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 cmd/yttests/main.go create mode 100644 internal/thirdparty/ytsearch/searchresult.go create mode 100644 internal/thirdparty/ytsearch/ytsearch.go diff --git a/cmd/feelbeatserver/main.go b/cmd/feelbeatserver/main.go index 25eabf2..5eef8e7 100644 --- a/cmd/feelbeatserver/main.go +++ b/cmd/feelbeatserver/main.go @@ -13,8 +13,8 @@ import ( ) const ( - ENV_PREFIX = "FEELBEAT_" - TOML_PATH = "config.toml" + envPrefix = "FEELBEAT_" + tomlPath = "config.toml" ) func main() { diff --git a/cmd/feelbeatserver/setupconfig.go b/cmd/feelbeatserver/setupconfig.go index 85b48d8..76e1868 100644 --- a/cmd/feelbeatserver/setupconfig.go +++ b/cmd/feelbeatserver/setupconfig.go @@ -14,7 +14,7 @@ import ( ) func setupConfig(config *koanf.Koanf) error { - err := config.Load(file.Provider(TOML_PATH), toml.Parser()) + err := config.Load(file.Provider(tomlPath), toml.Parser()) if err != nil { if !errors.Is(err, fs.ErrNotExist) { @@ -26,8 +26,8 @@ func setupConfig(config *koanf.Koanf) error { fblog.Info(component.Config, "config file loaded") } - envProvider := env.Provider(ENV_PREFIX, ".", func(key string) string { - envvar := strings.ToLower(strings.TrimPrefix(key, ENV_PREFIX)) + envProvider := env.Provider(envPrefix, ".", func(key string) string { + envvar := strings.ToLower(strings.TrimPrefix(key, envPrefix)) return strings.ReplaceAll(envvar, "_", ".") }) diff --git a/cmd/yttests/main.go b/cmd/yttests/main.go new file mode 100644 index 0000000..595dda6 --- /dev/null +++ b/cmd/yttests/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "log" + + "github.com/feelbeatapp/feelbeatserver/internal/thirdparty/ytsearch" +) + +func main() { + result, err := ytsearch.Search("System of a down Aerials") + if err != nil { + log.Fatal(err) + } + + for _, r := range result { + fmt.Printf("%v\n%v\n%v\n\n", r.VideoId, r.Title, r.Duration) + } +} diff --git a/go.mod b/go.mod index e31fba4..2a063a9 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( ) require ( + github.com/buger/jsonparser v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index 6b20e2a..9655e0d 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= diff --git a/internal/thirdparty/ytsearch/searchresult.go b/internal/thirdparty/ytsearch/searchresult.go new file mode 100644 index 0000000..774110a --- /dev/null +++ b/internal/thirdparty/ytsearch/searchresult.go @@ -0,0 +1,9 @@ +package ytsearch + +import "time" + +type SearchResult struct { + VideoId string + Title string + Duration time.Duration +} diff --git a/internal/thirdparty/ytsearch/ytsearch.go b/internal/thirdparty/ytsearch/ytsearch.go new file mode 100644 index 0000000..ddbb1f6 --- /dev/null +++ b/internal/thirdparty/ytsearch/ytsearch.go @@ -0,0 +1,121 @@ +package ytsearch + +import ( + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/buger/jsonparser" +) + +const ytBaseUrl = "https://www.youtube.com/results?search_query=" + +func fetchRawPage(query string) ([]byte, error) { + res, err := http.DefaultClient.Get(ytBaseUrl + url.QueryEscape(query)) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + rawHtml, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + return rawHtml, nil +} + +func extractJsonContent(rawHtml []byte) string { + firstSplits := strings.Split(string(rawHtml), `var ytInitialData = `) + return strings.Split(firstSplits[1], `;`)[0] + +} + +func searchVideosInJson(jsonString string) []byte { + index := 0 + + for { + vidoes, _, _, err := jsonparser.Get([]byte(jsonString), fmt.Sprintf("[%d]", index), "itemSectionRenderer", "contents") + if err == nil { + return vidoes + } + + index++ + } +} + +func parseJson(jsonString string) ([]SearchResult, error) { + videosSection, _, _, err := jsonparser.Get([]byte(jsonString), "contents", "twoColumnSearchResultsRenderer", "primaryContents", "sectionListRenderer", "contents") + videosListJson := searchVideosInJson(string(videosSection)) + if err != nil { + return nil, err + } + + result := make([]SearchResult, 0) + + _, err = jsonparser.ArrayEach(videosListJson, func(value []byte, _ jsonparser.ValueType, _ int, _ error) { + video, _, _, err := jsonparser.Get(value, "videoRenderer") + if err != nil { + return + } + + id, err := jsonparser.GetString(video, "videoId") + if err != nil { + return + } + title, err := jsonparser.GetString(video, "title", "runs", "[0]", "text") + if err != nil { + return + } + durationString, err := jsonparser.GetString(video, "lengthText", "simpleText") + if err != nil { + return + } + durations := strings.Split(durationString, ":") + if len(durations) > 2 { + return + } + + minutes, err := strconv.Atoi(durations[0]) + if err != nil { + return + } + + seconds, err := strconv.Atoi(durations[1]) + if err != nil { + return + } + + result = append(result, SearchResult{ + VideoId: id, + Title: title, + Duration: time.Duration(seconds)*time.Second + time.Duration(minutes)*time.Minute, + }) + }) + if err != nil { + return nil, err + } + + return result, nil +} + +func Search(query string) ([]SearchResult, error) { + rawHtml, err := fetchRawPage(query) + if err != nil { + return nil, fmt.Errorf("Youtube search failed: %w", err) + } + + jsonString := extractJsonContent(rawHtml) + + results, err := parseJson(jsonString) + if err != nil { + return nil, fmt.Errorf("Youtbe search parsing failed: %w", err) + } + + return results, nil +} From 08e6dc7e62ceb8c34080b04a5531e66a72ef8499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C4=99pie=C5=84?= Date: Fri, 20 Dec 2024 16:26:05 +0100 Subject: [PATCH 3/4] add loop treshold to youtube search --- cmd/yttests/main.go | 2 +- internal/thirdparty/ytsearch/ytsearch.go | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cmd/yttests/main.go b/cmd/yttests/main.go index 595dda6..e4e6ad0 100644 --- a/cmd/yttests/main.go +++ b/cmd/yttests/main.go @@ -8,7 +8,7 @@ import ( ) func main() { - result, err := ytsearch.Search("System of a down Aerials") + result, err := ytsearch.Search("Paris platynov freestyle") if err != nil { log.Fatal(err) } diff --git a/internal/thirdparty/ytsearch/ytsearch.go b/internal/thirdparty/ytsearch/ytsearch.go index ddbb1f6..923cdda 100644 --- a/internal/thirdparty/ytsearch/ytsearch.go +++ b/internal/thirdparty/ytsearch/ytsearch.go @@ -1,6 +1,7 @@ package ytsearch import ( + "errors" "fmt" "io" "net/http" @@ -13,6 +14,7 @@ import ( ) const ytBaseUrl = "https://www.youtube.com/results?search_query=" +const loopSearchTreshold = 5 func fetchRawPage(query string) ([]byte, error) { res, err := http.DefaultClient.Get(ytBaseUrl + url.QueryEscape(query)) @@ -36,22 +38,25 @@ func extractJsonContent(rawHtml []byte) string { } -func searchVideosInJson(jsonString string) []byte { - index := 0 - - for { +func searchVideosInJson(jsonString string) ([]byte, error) { + for index := 0; index < 5; index++ { vidoes, _, _, err := jsonparser.Get([]byte(jsonString), fmt.Sprintf("[%d]", index), "itemSectionRenderer", "contents") if err == nil { - return vidoes + return vidoes, nil } index++ } + + return nil, errors.New("Couldn't find results in youtube response") } func parseJson(jsonString string) ([]SearchResult, error) { videosSection, _, _, err := jsonparser.Get([]byte(jsonString), "contents", "twoColumnSearchResultsRenderer", "primaryContents", "sectionListRenderer", "contents") - videosListJson := searchVideosInJson(string(videosSection)) + if err != nil { + return nil, err + } + videosListJson, err := searchVideosInJson(string(videosSection)) if err != nil { return nil, err } From 170228310bfa9a74597981aa4fc69add6c0c8bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C4=99pie=C5=84?= Date: Sun, 22 Dec 2024 23:18:53 +0100 Subject: [PATCH 4/4] Restructure project, prototype youtube url extraction --- cmd/feelbeatserver/main.go | 2 +- cmd/yttests/main.go | 19 ------- go.mod | 9 ++- go.sum | 57 ++++++++++++++++++- internal/lib/audioprovider/ytaudioprovider.go | 4 ++ internal/lib/component/main.go | 2 + internal/lib/testutils/testutils.go | 23 ++++++++ internal/lib/types.go | 9 +++ internal/thirdparty/spotify/spotifyapi.go | 47 +++++++++++++++ .../ytsearch.go => youtube/search.go} | 10 +++- internal/thirdparty/youtube/urlextractor.go | 41 +++++++++++++ internal/thirdparty/ytsearch/searchresult.go | 9 --- 12 files changed, 197 insertions(+), 35 deletions(-) delete mode 100644 cmd/yttests/main.go create mode 100644 internal/lib/audioprovider/ytaudioprovider.go create mode 100644 internal/lib/testutils/testutils.go create mode 100644 internal/lib/types.go create mode 100644 internal/thirdparty/spotify/spotifyapi.go rename internal/thirdparty/{ytsearch/ytsearch.go => youtube/search.go} (94%) create mode 100644 internal/thirdparty/youtube/urlextractor.go delete mode 100644 internal/thirdparty/ytsearch/searchresult.go diff --git a/cmd/feelbeatserver/main.go b/cmd/feelbeatserver/main.go index 5eef8e7..125c74f 100644 --- a/cmd/feelbeatserver/main.go +++ b/cmd/feelbeatserver/main.go @@ -6,9 +6,9 @@ import ( "net/http" "os" - "github.com/feelbeatapp/feelbeatserver/internal/lib/component" "github.com/feelbeatapp/feelbeatserver/internal/infra/fblog" "github.com/feelbeatapp/feelbeatserver/internal/infra/ws" + "github.com/feelbeatapp/feelbeatserver/internal/lib/component" "github.com/knadh/koanf/v2" ) diff --git a/cmd/yttests/main.go b/cmd/yttests/main.go deleted file mode 100644 index e4e6ad0..0000000 --- a/cmd/yttests/main.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "github.com/feelbeatapp/feelbeatserver/internal/thirdparty/ytsearch" -) - -func main() { - result, err := ytsearch.Search("Paris platynov freestyle") - if err != nil { - log.Fatal(err) - } - - for _, r := range result { - fmt.Printf("%v\n%v\n%v\n\n", r.VideoId, r.Title, r.Duration) - } -} diff --git a/go.mod b/go.mod index 2a063a9..3935de7 100644 --- a/go.mod +++ b/go.mod @@ -5,16 +5,19 @@ go 1.23.1 require github.com/gorilla/websocket v1.5.3 require ( + github.com/buger/jsonparser v1.1.1 github.com/knadh/koanf/parsers/toml v0.1.0 github.com/knadh/koanf/providers/env v1.0.0 github.com/knadh/koanf/providers/file v1.1.2 github.com/knadh/koanf/v2 v2.1.2 github.com/lmittmann/tint v1.0.5 + github.com/lrstanley/go-ytdlp v0.0.0-20241221063727-6717edbb36dd github.com/stretchr/testify v1.9.0 ) require ( - github.com/buger/jsonparser v1.1.1 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect @@ -23,6 +26,8 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.27.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/sys v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9655e0d..5f7779b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,11 @@ +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= @@ -24,6 +30,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw= github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/lrstanley/go-ytdlp v0.0.0-20241221063727-6717edbb36dd h1:lLajTMgNTs/W4H05uQYnJDRIbIvHk6XXy7DQNFRbvzU= +github.com/lrstanley/go-ytdlp v0.0.0-20241221063727-6717edbb36dd/go.mod h1:75ujbafjqiJugIGw4K6o52/p8C0m/kt+DrYwgClXYT4= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -32,10 +40,55 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/lib/audioprovider/ytaudioprovider.go b/internal/lib/audioprovider/ytaudioprovider.go new file mode 100644 index 0000000..19e0c5d --- /dev/null +++ b/internal/lib/audioprovider/ytaudioprovider.go @@ -0,0 +1,4 @@ +package audioprovider + +type YTAudioProvider struct { +} diff --git a/internal/lib/component/main.go b/internal/lib/component/main.go index 57bb528..8bf7246 100644 --- a/internal/lib/component/main.go +++ b/internal/lib/component/main.go @@ -6,6 +6,8 @@ const ( WebSocket = "websocket" Client = "client" Hub = "hub" + + AudioDownloadTask = "audiodownloadtask" ) type FeelBeatComponent = string diff --git a/internal/lib/testutils/testutils.go b/internal/lib/testutils/testutils.go new file mode 100644 index 0000000..c89b620 --- /dev/null +++ b/internal/lib/testutils/testutils.go @@ -0,0 +1,23 @@ +package testutils + +import ( + "errors" + "sync" + "time" +) + +// waitTimeout waits for the waitgroup for the specified max timeout. +// Returns true if waiting timed out. +func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) error { + c := make(chan struct{}) + go func() { + defer close(c) + wg.Wait() + }() + select { + case <-c: + return nil // completed normally + case <-time.After(timeout): + return errors.New("Timeout exceeded") // timed out + } +} diff --git a/internal/lib/types.go b/internal/lib/types.go new file mode 100644 index 0000000..cbe7d65 --- /dev/null +++ b/internal/lib/types.go @@ -0,0 +1,9 @@ +package lib + +import "time" + +type SongDetails struct { + Title string + Artist string + Duration time.Duration +} diff --git a/internal/thirdparty/spotify/spotifyapi.go b/internal/thirdparty/spotify/spotifyapi.go new file mode 100644 index 0000000..b594c62 --- /dev/null +++ b/internal/thirdparty/spotify/spotifyapi.go @@ -0,0 +1,47 @@ +package spotify + +import ( + "fmt" + "io" + "log" + "net/http" + "time" + + "github.com/buger/jsonparser" + "github.com/feelbeatapp/feelbeatserver/internal/lib" +) + +func FetchSongDetails(spotifyId string, token string) { + url := fmt.Sprintf("https://api.spotify.com/v1/tracks/%s", spotifyId) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + req.Header.Set("Authorization", "Bearer "+token) + + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + + defer res.Body.Close() + bytes, err := io.ReadAll(res.Body) + if err != nil { + log.Fatal(err) + } + + title, err := jsonparser.GetString(bytes, "name") + if err != nil { + log.Fatal(err) + + } + durationInMs, err := jsonparser.GetInt(bytes, "duration_ms") + if err != nil { + log.Fatal(err) + } + + fmt.Println(lib.SongDetails{ + Title: title, + Duration: time.Duration(durationInMs) * time.Millisecond, + }) +} diff --git a/internal/thirdparty/ytsearch/ytsearch.go b/internal/thirdparty/youtube/search.go similarity index 94% rename from internal/thirdparty/ytsearch/ytsearch.go rename to internal/thirdparty/youtube/search.go index 923cdda..e0bdbad 100644 --- a/internal/thirdparty/ytsearch/ytsearch.go +++ b/internal/thirdparty/youtube/search.go @@ -1,4 +1,4 @@ -package ytsearch +package youtube import ( "errors" @@ -13,6 +13,12 @@ import ( "github.com/buger/jsonparser" ) +type SearchResult struct { + VideoId string + Title string + Duration time.Duration +} + const ytBaseUrl = "https://www.youtube.com/results?search_query=" const loopSearchTreshold = 5 @@ -39,7 +45,7 @@ func extractJsonContent(rawHtml []byte) string { } func searchVideosInJson(jsonString string) ([]byte, error) { - for index := 0; index < 5; index++ { + for index := 0; index < loopSearchTreshold; index++ { vidoes, _, _, err := jsonparser.Get([]byte(jsonString), fmt.Sprintf("[%d]", index), "itemSectionRenderer", "contents") if err == nil { return vidoes, nil diff --git a/internal/thirdparty/youtube/urlextractor.go b/internal/thirdparty/youtube/urlextractor.go new file mode 100644 index 0000000..677249d --- /dev/null +++ b/internal/thirdparty/youtube/urlextractor.go @@ -0,0 +1,41 @@ +package youtube + +import ( + "context" + "strings" + + "github.com/lrstanley/go-ytdlp" +) + +type YTUrlExtractor struct { + initialized bool +} + +func NewYTUrlExtractor() *YTUrlExtractor { + return &YTUrlExtractor{ + initialized: false, + } +} + +func (e *YTUrlExtractor) Init(ctx context.Context) { + if e.initialized { + return + } + + ytdlp.MustInstall(ctx, nil) + e.initialized = true +} + +func (e *YTUrlExtractor) GetDirectUrl(ctx context.Context, url string) (string, error) { + dl := ytdlp.New().GetURL() + output, err := dl.Run(ctx, url) + if err != nil { + return "", err + } + if ctx.Err() != nil { + return "", ctx.Err() + } + + splits := strings.Split(output.Stdout, "https://") + return "https://" + splits[2], nil +} diff --git a/internal/thirdparty/ytsearch/searchresult.go b/internal/thirdparty/ytsearch/searchresult.go deleted file mode 100644 index 774110a..0000000 --- a/internal/thirdparty/ytsearch/searchresult.go +++ /dev/null @@ -1,9 +0,0 @@ -package ytsearch - -import "time" - -type SearchResult struct { - VideoId string - Title string - Duration time.Duration -}