package subsonic import ( "context" "database/sql" "git.noroutes.net/NoRoutes/vibetunes/internal/database" ) func init() { register("getPodcasts", handleGetPodcasts) register("getNewestPodcasts", handleGetNewestPodcasts) register("refreshPodcasts", handleRefreshPodcasts) register("createPodcastChannel", handleCreatePodcastChannel) register("deletePodcastChannel", handleDeletePodcastChannel) register("deletePodcastEpisode", handleDeletePodcastEpisode) register("downloadPodcastEpisode", handleDownloadPodcastEpisode) } func podcastEpisodeToModel(db *sql.DB, e *database.PodcastEpisode, userID string) PodcastEpisode { pe := PodcastEpisode{ Child: Child{ ID: e.ID, Title: e.Title, Album: e.Title, Genre: "Podcast", Type: "podcast", Created: isoTime(e.CreatedAt), }, ChannelID: e.ChannelID, Description: e.Description, Status: e.Status, PublishDate: e.PublishDate, } if e.MediaFileID.Valid { if mf, err := database.GetMediaFile(db, e.MediaFileID.String); err == nil { pe.Child.Duration = int(mf.DurationSeconds + 0.5) pe.Child.Size = mf.SizeBytes pe.Child.Suffix = suffixForFormat(mf.Format) pe.Child.ContentType = contentTypeForFormat(mf.Format) // Once downloaded, an episode streams as an ordinary song // (same /rest/stream path, permission checks, everything). if song, serr := database.GetSongByMediaFileID(db, mf.ID); serr == nil { pe.Child.ID = song.ID pe.StreamID = song.ID } } } else { pe.Child.Duration = int(e.DurationSeconds + 0.5) } return pe } func podcastChannelToModel(db *sql.DB, c *database.PodcastChannel, withEpisodes bool, userID string) (PodcastChannel, error) { pc := PodcastChannel{ ID: c.ID, URL: c.URL, Title: c.Title, Description: c.Description, Status: c.Status, ErrorMessage: c.ErrorMessage, } if c.CoverArtID.Valid { pc.CoverArt = c.CoverArtID.String } if withEpisodes { episodes, err := database.ListPodcastEpisodes(db, c.ID) if err != nil { return pc, err } for _, e := range episodes { pc.Episode = append(pc.Episode, podcastEpisodeToModel(db, e, userID)) } } return pc, nil } func handleGetPodcasts(rc *Context) *apiError { if !rc.User.Allows(rc.User.Permissions.Podcast) { return errUnauthorized("podcasts") } db := rc.Server.DB withEpisodes := rc.Params.Bool("includeEpisodes", true) out := &Podcasts{} if id := rc.Params.Get("id"); id != "" { channel, err := database.GetPodcastChannel(db, id) if err != nil { return errNotFound("Podcast channel") } model, merr := podcastChannelToModel(db, channel, withEpisodes, rc.User.ID) if merr != nil { return newError(ErrGeneric, merr.Error()) } out.Channel = append(out.Channel, model) } else { channels, err := database.ListPodcastChannels(db) if err != nil { return newError(ErrGeneric, err.Error()) } for _, c := range channels { model, merr := podcastChannelToModel(db, c, withEpisodes, rc.User.ID) if merr != nil { continue } out.Channel = append(out.Channel, model) } } rc.Response = newResponse() rc.Response.Podcasts = out return nil } func handleGetNewestPodcasts(rc *Context) *apiError { if !rc.User.Allows(rc.User.Permissions.Podcast) { return errUnauthorized("podcasts") } count := rc.Params.Int("count", 20) episodes, err := database.ListNewestPodcastEpisodes(rc.Server.DB, count) if err != nil { return newError(ErrGeneric, err.Error()) } out := &NewestPodcasts{} for _, e := range episodes { out.Episode = append(out.Episode, podcastEpisodeToModel(rc.Server.DB, e, rc.User.ID)) } rc.Response = newResponse() rc.Response.NewestPodcasts = out return nil } func handleRefreshPodcasts(rc *Context) *apiError { if !rc.User.IsAdmin { return errUnauthorized("refreshing podcasts") } channels, err := database.ListPodcastChannels(rc.Server.DB) if err != nil { return newError(ErrGeneric, err.Error()) } for _, c := range channels { id := c.ID go func() { _ = rc.Server.Podcasts.RefreshChannel(context.Background(), id) }() } rc.Response = newResponse() return nil } func handleCreatePodcastChannel(rc *Context) *apiError { if !rc.User.IsAdmin { return errUnauthorized("managing podcasts") } feedURL, aerr := rc.Params.Required("url") if aerr != nil { return aerr } channel, err := database.CreatePodcastChannel(rc.Server.DB, feedURL) if err != nil { return newError(ErrGeneric, err.Error()) } go func() { _ = rc.Server.Podcasts.RefreshChannel(context.Background(), channel.ID) }() rc.Response = newResponse() return nil } func handleDeletePodcastChannel(rc *Context) *apiError { if !rc.User.IsAdmin { return errUnauthorized("managing podcasts") } id, aerr := rc.Params.Required("id") if aerr != nil { return aerr } if _, err := database.GetPodcastChannel(rc.Server.DB, id); err != nil { return errNotFound("Podcast channel") } if err := database.DeletePodcastChannel(rc.Server.DB, id); err != nil { return newError(ErrGeneric, err.Error()) } rc.Response = newResponse() return nil } func handleDeletePodcastEpisode(rc *Context) *apiError { if !rc.User.IsAdmin { return errUnauthorized("managing podcasts") } id, aerr := rc.Params.Required("id") if aerr != nil { return aerr } if _, err := database.GetPodcastEpisode(rc.Server.DB, id); err != nil { return errNotFound("Podcast episode") } if err := database.DeletePodcastEpisode(rc.Server.DB, id); err != nil { return newError(ErrGeneric, err.Error()) } rc.Response = newResponse() return nil } func handleDownloadPodcastEpisode(rc *Context) *apiError { if !rc.User.IsAdmin { return errUnauthorized("managing podcasts") } id, aerr := rc.Params.Required("id") if aerr != nil { return aerr } if _, err := database.GetPodcastEpisode(rc.Server.DB, id); err != nil { return errNotFound("Podcast episode") } go func() { if err := rc.Server.Podcasts.DownloadEpisode(context.Background(), id); err != nil { rc.Server.Log.Warn("podcast episode download failed", "episode", id, "err", err) } }() rc.Response = newResponse() return nil }