Added OPML Endpoint for podcast rss feeds (#512)
* Added OPML Endpoint for podcast rss feeds * changed opml route and added controller test for opml endpoint * add copy opml feed button * add copy opml feed button - correct url * fix html indenting * add indentation to opml Co-authored-by: Kieran <kieran.eglin@gmail.com> * use convention for unused controller params Co-authored-by: Kieran <kieran.eglin@gmail.com> * add test for opml_sources helper function * change opml endpoint to be more inline with the other routes --------- Co-authored-by: robs <git@robs.social> Co-authored-by: Kieran <kieran.eglin@gmail.com>
This commit is contained in:
committed by
GitHub
parent
a2a70fcce2
commit
c9bd1ea7bd
@@ -0,0 +1,40 @@
|
||||
defmodule Pinchflat.Podcasts.OpmlFeedBuilder do
|
||||
@moduledoc """
|
||||
Methods for building an OPML feed for a list of sources.
|
||||
"""
|
||||
|
||||
import Pinchflat.Utils.XmlUtils, only: [safe: 1]
|
||||
|
||||
alias PinchflatWeb.Router.Helpers, as: Routes
|
||||
|
||||
@doc """
|
||||
Builds an OPML feed for a given list of sources.
|
||||
|
||||
Returns an XML document as a string.
|
||||
"""
|
||||
def build(url_base, sources) do
|
||||
sources_xml =
|
||||
Enum.map(
|
||||
sources,
|
||||
&"""
|
||||
<outline type="rss" text="#{safe(&1.custom_name)}" xmlUrl="#{safe(source_route(url_base, &1))}" />
|
||||
"""
|
||||
)
|
||||
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<opml version="2.0">
|
||||
<head>
|
||||
<title>All Sources</title>
|
||||
</head>
|
||||
<body>
|
||||
#{Enum.join(sources_xml, "\n")}
|
||||
</body>
|
||||
</opml>
|
||||
"""
|
||||
end
|
||||
|
||||
defp source_route(url_base, source) do
|
||||
Path.join(url_base, "#{Routes.podcast_path(PinchflatWeb.Endpoint, :rss_feed, source.uuid)}.xml")
|
||||
end
|
||||
end
|
||||
@@ -5,11 +5,25 @@ defmodule Pinchflat.Podcasts.PodcastHelpers do
|
||||
"""
|
||||
|
||||
use Pinchflat.Media.MediaQuery
|
||||
use Pinchflat.Sources.SourcesQuery
|
||||
|
||||
alias Pinchflat.Repo
|
||||
alias Pinchflat.Metadata.MediaMetadata
|
||||
alias Pinchflat.Metadata.SourceMetadata
|
||||
|
||||
@doc """
|
||||
Returns a list of sources that are not marked for deletion.
|
||||
|
||||
Returns: [%Source{}]
|
||||
"""
|
||||
def opml_sources() do
|
||||
SourcesQuery.new()
|
||||
|> select([s], %{custom_name: s.custom_name, uuid: s.uuid})
|
||||
|> where([s], is_nil(s.marked_for_deletion_at))
|
||||
|> order_by(asc: :custom_name)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of media items that have been downloaded to disk
|
||||
and have been proven to still exist there.
|
||||
|
||||
@@ -6,8 +6,19 @@ defmodule PinchflatWeb.Podcasts.PodcastController do
|
||||
alias Pinchflat.Sources.Source
|
||||
alias Pinchflat.Media.MediaItem
|
||||
alias Pinchflat.Podcasts.RssFeedBuilder
|
||||
alias Pinchflat.Podcasts.OpmlFeedBuilder
|
||||
alias Pinchflat.Podcasts.PodcastHelpers
|
||||
|
||||
def opml_feed(conn, _params) do
|
||||
url_base = url(conn, ~p"/")
|
||||
xml = OpmlFeedBuilder.build(url_base, PodcastHelpers.opml_sources())
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/opml+xml")
|
||||
|> put_resp_header("content-disposition", "inline")
|
||||
|> send_resp(200, xml)
|
||||
end
|
||||
|
||||
def rss_feed(conn, %{"uuid" => uuid}) do
|
||||
source = Repo.get_by!(Source, uuid: uuid)
|
||||
url_base = url(conn, ~p"/")
|
||||
|
||||
@@ -43,6 +43,10 @@ defmodule PinchflatWeb.Sources.SourceHTML do
|
||||
url(conn, ~p"/sources/#{source.uuid}/feed") <> ".xml"
|
||||
end
|
||||
|
||||
def opml_feed_url(conn) do
|
||||
url(conn, ~p"/sources/opml") <> ".xml"
|
||||
end
|
||||
|
||||
def output_path_template_override_placeholders(media_profiles) do
|
||||
media_profiles
|
||||
|> Enum.map(&{&1.id, &1.output_path_template})
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
<div class="mb-6 flex gap-3 flex-row items-center justify-between">
|
||||
<h2 class="text-title-md2 font-bold text-black dark:text-white">Sources</h2>
|
||||
<nav>
|
||||
<.button color="bg-transparent" x-data="{ copied: false }" x-on:click={~s"
|
||||
copyWithCallbacks(
|
||||
'#{opml_feed_url(@conn)}',
|
||||
() => copied = true,
|
||||
() => copied = false
|
||||
)
|
||||
"}>
|
||||
Copy OPML Feed
|
||||
<span x-show="copied" x-transition.duration.150ms><.icon name="hero-check" class="ml-2 h-4 w-4" /></span>
|
||||
</.button>
|
||||
<.link href={~p"/sources/new"}>
|
||||
<.button color="bg-primary" rounding="rounded-lg">
|
||||
<span class="font-bold mx-2">+</span> New <span class="hidden sm:inline pl-1">Source</span>
|
||||
|
||||
+14
-12
@@ -23,6 +23,20 @@ defmodule PinchflatWeb.Router do
|
||||
plug :maybe_basic_auth
|
||||
end
|
||||
|
||||
# Routes in here _may not be_ protected by basic auth. This is necessary for
|
||||
# media streaming to work for RSS podcast feeds.
|
||||
scope "/", PinchflatWeb do
|
||||
pipe_through :feeds
|
||||
# has to match before /sources/:id
|
||||
get "/sources/opml", Podcasts.PodcastController, :opml_feed
|
||||
|
||||
get "/sources/:uuid/feed", Podcasts.PodcastController, :rss_feed
|
||||
get "/sources/:uuid/feed_image", Podcasts.PodcastController, :feed_image
|
||||
get "/media/:uuid/episode_image", Podcasts.PodcastController, :episode_image
|
||||
|
||||
get "/media/:uuid/stream", MediaItems.MediaItemController, :stream
|
||||
end
|
||||
|
||||
scope "/", PinchflatWeb do
|
||||
pipe_through :browser
|
||||
|
||||
@@ -48,18 +62,6 @@ defmodule PinchflatWeb.Router do
|
||||
end
|
||||
end
|
||||
|
||||
# Routes in here _may not be_ protected by basic auth. This is necessary for
|
||||
# media streaming to work for RSS podcast feeds.
|
||||
scope "/", PinchflatWeb do
|
||||
pipe_through :feeds
|
||||
|
||||
get "/sources/:uuid/feed", Podcasts.PodcastController, :rss_feed
|
||||
get "/sources/:uuid/feed_image", Podcasts.PodcastController, :feed_image
|
||||
get "/media/:uuid/episode_image", Podcasts.PodcastController, :episode_image
|
||||
|
||||
get "/media/:uuid/stream", MediaItems.MediaItemController, :stream
|
||||
end
|
||||
|
||||
# No auth or CSRF protection for the health check endpoint
|
||||
scope "/", PinchflatWeb do
|
||||
pipe_through :api
|
||||
|
||||
Reference in New Issue
Block a user