Added OPML Endpoint for podcast rss feeds
This commit is contained in:
parent
53e106dac2
commit
a1ae215dd4
5 changed files with 101 additions and 0 deletions
40
lib/pinchflat/podcasts/opml_feed_builder.ex
Normal file
40
lib/pinchflat/podcasts/opml_feed_builder.ex
Normal file
|
|
@ -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.Media.MediaQuery
|
||||||
|
use Pinchflat.Sources.SourcesQuery
|
||||||
|
|
||||||
alias Pinchflat.Repo
|
alias Pinchflat.Repo
|
||||||
alias Pinchflat.Metadata.MediaMetadata
|
alias Pinchflat.Metadata.MediaMetadata
|
||||||
alias Pinchflat.Metadata.SourceMetadata
|
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 """
|
@doc """
|
||||||
Returns a list of media items that have been downloaded to disk
|
Returns a list of media items that have been downloaded to disk
|
||||||
and have been proven to still exist there.
|
and have been proven to still exist there.
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,19 @@ defmodule PinchflatWeb.Podcasts.PodcastController do
|
||||||
alias Pinchflat.Sources.Source
|
alias Pinchflat.Sources.Source
|
||||||
alias Pinchflat.Media.MediaItem
|
alias Pinchflat.Media.MediaItem
|
||||||
alias Pinchflat.Podcasts.RssFeedBuilder
|
alias Pinchflat.Podcasts.RssFeedBuilder
|
||||||
|
alias Pinchflat.Podcasts.OpmlFeedBuilder
|
||||||
alias Pinchflat.Podcasts.PodcastHelpers
|
alias Pinchflat.Podcasts.PodcastHelpers
|
||||||
|
|
||||||
|
def opml_feed(conn, %{}) 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
|
def rss_feed(conn, %{"uuid" => uuid}) do
|
||||||
source = Repo.get_by!(Source, uuid: uuid)
|
source = Repo.get_by!(Source, uuid: uuid)
|
||||||
url_base = url(conn, ~p"/")
|
url_base = url(conn, ~p"/")
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ defmodule PinchflatWeb.Router do
|
||||||
scope "/", PinchflatWeb do
|
scope "/", PinchflatWeb do
|
||||||
pipe_through :feeds
|
pipe_through :feeds
|
||||||
|
|
||||||
|
get "/opml", Podcasts.PodcastController, :opml_feed
|
||||||
|
|
||||||
get "/sources/:uuid/feed", Podcasts.PodcastController, :rss_feed
|
get "/sources/:uuid/feed", Podcasts.PodcastController, :rss_feed
|
||||||
get "/sources/:uuid/feed_image", Podcasts.PodcastController, :feed_image
|
get "/sources/:uuid/feed_image", Podcasts.PodcastController, :feed_image
|
||||||
get "/media/:uuid/episode_image", Podcasts.PodcastController, :episode_image
|
get "/media/:uuid/episode_image", Podcasts.PodcastController, :episode_image
|
||||||
|
|
|
||||||
34
test/pinchflat/podcasts/opml_feed_builder_test.exs
Normal file
34
test/pinchflat/podcasts/opml_feed_builder_test.exs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
defmodule Pinchflat.Podcasts.OpmlFeedBuilderTest do
|
||||||
|
use Pinchflat.DataCase
|
||||||
|
|
||||||
|
import Pinchflat.SourcesFixtures
|
||||||
|
|
||||||
|
alias Pinchflat.Podcasts.OpmlFeedBuilder
|
||||||
|
|
||||||
|
setup do
|
||||||
|
source = source_fixture()
|
||||||
|
|
||||||
|
{:ok, source: source}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "build/2" do
|
||||||
|
test "returns an XML document", %{source: source} do
|
||||||
|
res = OpmlFeedBuilder.build("http://example.com", [source])
|
||||||
|
|
||||||
|
assert String.contains?(res, ~s(<?xml version="1.0" encoding="UTF-8"?>))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "escapes illegal characters" do
|
||||||
|
source = source_fixture(%{custom_name: "A & B"})
|
||||||
|
res = OpmlFeedBuilder.build("http://example.com", [source])
|
||||||
|
|
||||||
|
assert String.contains?(res, ~s(A & B))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "build podcast link with URL base", %{source: source} do
|
||||||
|
res = OpmlFeedBuilder.build("http://example.com", [source])
|
||||||
|
|
||||||
|
assert String.contains?(res, ~s(http://example.com/sources/#{source.uuid}/feed.xml))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue