From a08067f1d707a3c7107147b281f590073cb8744b Mon Sep 17 00:00:00 2001 From: Jesse Michael Date: Thu, 7 Sep 2023 15:55:34 -0700 Subject: [PATCH] feat: update soundcloud feeder --- cmd/fetcher/main.go | 16 +++++++- internal/service/fetcher.go | 11 +++--- internal/service/soundcloud.go | 70 ++++++++++++++++++++++++++++++---- 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/cmd/fetcher/main.go b/cmd/fetcher/main.go index 99efcb7..3226850 100644 --- a/cmd/fetcher/main.go +++ b/cmd/fetcher/main.go @@ -5,6 +5,7 @@ import ( "log/slog" "os" "os/signal" + "strings" "syscall" "github.com/jesse0michael/fetcher/internal/server" @@ -19,7 +20,7 @@ type Config struct { } func main() { - slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: LogLevel()}))) // Setup context that will cancel on signalled termination ctx, cancel := context.WithCancel(context.Background()) @@ -52,3 +53,16 @@ func main() { srvr.Close() slog.Info("exiting") } + +func LogLevel() slog.Leveler { + switch strings.ToUpper(os.Getenv("LOG_LEVEL")) { + case "DEBUG": + return slog.LevelDebug + case "WARN": + return slog.LevelWarn + case "ERROR": + return slog.LevelError + default: + return slog.LevelInfo + } +} diff --git a/internal/service/fetcher.go b/internal/service/fetcher.go index 275bf26..78673eb 100644 --- a/internal/service/fetcher.go +++ b/internal/service/fetcher.go @@ -8,10 +8,11 @@ import ( ) type Config struct { - Twitter TwitterConfig - Instagram InstagramConfig - Count int `envconfig:"FETCHER_COUNT" default:"50"` - ProxyURL string `envconfig:"FETCHER_PROXY_URL" default:"https://fetcher-ho4joes5va-uw.a.run.app/proxy"` + Twitter TwitterConfig + Instagram InstagramConfig + SoundCloud SoundCloudConfig + Count int `envconfig:"FETCHER_COUNT" default:"50"` + ProxyURL string `envconfig:"FETCHER_PROXY_URL" default:"https://fetcher-ho4joes5va-uw.a.run.app/proxy"` } type Feeder interface { @@ -56,7 +57,7 @@ func NewFetcher(cfg Config) *Fetcher { blogger: NewBlogger(), twitter: twitter, instagram: instagram, - soundCloud: NewSoundCloud(), + soundCloud: NewSoundCloud(cfg.SoundCloud), swarm: NewSwarm(), deviantArt: NewDeviantArt(), } diff --git a/internal/service/soundcloud.go b/internal/service/soundcloud.go index d019e84..7e7f380 100644 --- a/internal/service/soundcloud.go +++ b/internal/service/soundcloud.go @@ -4,30 +4,84 @@ import ( "context" "fmt" "io" + "log/slog" "net/http" "net/url" + "strconv" "time" "github.com/tidwall/gjson" ) +type SoundCloudConfig struct { + Count int `envconfig:"SOUND_CLOUD_COUNT" default:"20"` + ClientID string `envconfig:"SOUND_CLOUD_CLIENT_ID"` + ClientSecret string `envconfig:"SOUND_CLOUD_CLIENT_SECRET"` +} + type SoundCloud struct { + cfg SoundCloudConfig + authToken string +} + +func NewSoundCloud(cfg SoundCloudConfig) *SoundCloud { + return &SoundCloud{cfg: cfg} } -func NewSoundCloud() *SoundCloud { - return &SoundCloud{} +func (s *SoundCloud) auth(ctx context.Context) (string, error) { + if s.authToken != "" { + return s.authToken, nil + } + + auth, err := s.authenticate(ctx) + if err != nil { + return "", err + } + s.authToken = auth + return auth, nil +} + +func (s *SoundCloud) authenticate(ctx context.Context) (string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, + "https://api.soundcloud.com/oauth2/token", nil) + if err != nil { + return "", err + } + q := url.Values{} + q.Add("client_id", s.cfg.ClientID) + q.Add("client_secret", s.cfg.ClientSecret) + q.Add("grant_type", "client_credentials") + req.URL.RawQuery = q.Encode() + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + slog.With("feeder", "soundcloud", "body", string(body), "status", resp.StatusCode). + Debug("soundcloud authentication response") + + return gjson.GetBytes(body, "access_token").String(), nil } -func (s *SoundCloud) Feed(_ context.Context, id string) ([]FeedItem, error) { - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, - fmt.Sprintf("https://api.soundcloud.com/users/%s/favorites", id), nil) +func (s *SoundCloud) Feed(ctx context.Context, id string) ([]FeedItem, error) { + auth, err := s.auth(ctx) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, + fmt.Sprintf("https://api.soundcloud.com/users/%s/likes/tracks", id), nil) if err != nil { return nil, err } q := url.Values{} - q.Add("client_id", "f330c0bb90f1c89a15e78ece83e21856") - q.Add("limit", "20") + q.Add("limit", strconv.Itoa(s.cfg.Count)) req.URL.RawQuery = q.Encode() + req.Header.Set("Authorization", fmt.Sprintf("OAuth %s", auth)) resp, err := http.DefaultClient.Do(req) if err != nil { @@ -38,6 +92,8 @@ func (s *SoundCloud) Feed(_ context.Context, id string) ([]FeedItem, error) { if err != nil { return nil, err } + slog.With("feeder", "soundcloud", "body", string(body), "status", resp.StatusCode). + Debug("soundcloud response") items := []FeedItem{} for _, sound := range gjson.ParseBytes(body).Array() {