Added module for working with youtube RSS feed

This commit is contained in:
Kieran Eglin 2024-03-08 11:46:59 -08:00
parent de18b67480
commit 4b63a39883
No known key found for this signature in database
GPG key ID: 193984967FCF432D
5 changed files with 125 additions and 4 deletions

View file

@ -1,10 +1,10 @@
alias Pinchflat.Repo alias Pinchflat.Repo
alias Pinchflat.Tasks.Task alias Pinchflat.Tasks.Task
alias Pinchflat.Sources.Source
alias Pinchflat.Media.MediaItem alias Pinchflat.Media.MediaItem
alias Pinchflat.Tasks.SourceTasks alias Pinchflat.Tasks.SourceTasks
alias Pinchflat.Media.MediaMetadata alias Pinchflat.Media.MediaMetadata
alias Pinchflat.Sources.Source
alias Pinchflat.Profiles.MediaProfile alias Pinchflat.Profiles.MediaProfile
alias Pinchflat.Tasks alias Pinchflat.Tasks
@ -13,7 +13,11 @@ alias Pinchflat.Profiles
alias Pinchflat.Sources alias Pinchflat.Sources
alias Pinchflat.Settings alias Pinchflat.Settings
alias Pinchflat.MediaClient.{SourceDetails, MediaDownloader} alias Pinchflat.MediaClient.MediaDownloader
alias Pinchflat.YtDlp.Backend.Media, as: YtDlpMedia
alias Pinchflat.YtDlp.Backend.MediaCollection, as: YtDlpCollection
alias Pinchflat.Api.YoutubeRss
alias Pinchflat.Metadata.MetadataFileHelpers alias Pinchflat.Metadata.MetadataFileHelpers
alias Pinchflat.Utils.FilesystemUtils.FileFollowerServer alias Pinchflat.Utils.FilesystemUtils.FileFollowerServer
@ -38,7 +42,7 @@ defmodule IexHelpers do
:channel -> channel_url() :channel -> channel_url()
end end
SourceDetails.get_source_details(source) YtDlpCollection.get_source_details(source)
end end
def ids(type) do def ids(type) do
@ -48,7 +52,7 @@ defmodule IexHelpers do
:channel -> channel_url() :channel -> channel_url()
end end
SourceDetails.get_media_attributes_for_collection(source) YtDlpCollection.get_media_attributes_for_collection(source)
end end
end end

View file

@ -0,0 +1,44 @@
defmodule Pinchflat.Api.YoutubeRss do
@moduledoc """
Methods for interacting with YouTube RSS feeds
"""
alias Pinchflat.Sources.Source
@doc """
Fetches the recent media IDs from a YouTube RSS feed for a given source.
Returns {:ok, [binary()]} | {:error, binary()}
"""
def get_recent_media_ids_from_rss(%Source{} = source) do
case http_client().get(rss_url_for_source(source)) do
{:ok, response} ->
response = to_string(response)
media_id_regex = ~r/<yt:videoId>(.*?)<\/yt:videoId>/
# Don't get on me about using regex to search XML.
# The content is known, well-formed, and simple.
media_ids =
media_id_regex
|> Regex.scan(response)
|> Enum.map(fn [_, id] -> String.trim(id) end)
|> Enum.filter(&(String.length(&1) > 0))
{:ok, media_ids}
{:error, _reason} ->
{:error, "Failed to fetch RSS feed"}
end
end
defp rss_url_for_source(source) do
case source.collection_type do
:channel -> "https://www.youtube.com/feeds/videos.xml?channel_id=#{source.collection_id}"
:playlist -> "https://www.youtube.com/feeds/videos.xml?playlist_id=#{source.collection_id}"
end
end
defp http_client do
Application.get_env(:pinchflat, :http_client, Pinchflat.HTTP.HTTPClient)
end
end

View file

@ -4,5 +4,7 @@ defmodule Pinchflat.HTTP.HTTPBehaviour do
so I can use Mox to create an HTTP mock so I can use Mox to create an HTTP mock
""" """
@callback get(String.t()) :: {:ok, String.t()} | {:error, String.t()}
@callback get(String.t(), Keyword.t()) :: {:ok, String.t()} | {:error, String.t()}
@callback get(String.t(), Keyword.t(), Keyword.t()) :: {:ok, String.t()} | {:error, String.t()} @callback get(String.t(), Keyword.t(), Keyword.t()) :: {:ok, String.t()} | {:error, String.t()}
end end

View file

@ -0,0 +1,71 @@
defmodule Pinchflat.Api.YoutubeRssTest do
use Pinchflat.DataCase
import Mox
import Pinchflat.SourcesFixtures
alias Pinchflat.Api.YoutubeRss
setup :verify_on_exit!
setup do
source = source_fixture()
{:ok, source: source}
end
describe "get_recent_media_ids_from_rss/1" do
test "calls the expected URL for channel sources" do
source = source_fixture(collection_type: :channel, collection_id: "channel_id")
expect(HTTPClientMock, :get, fn url ->
assert url =~ "https://www.youtube.com/feeds/videos.xml?channel_id=#{source.collection_id}"
{:ok, ""}
end)
assert {:ok, _} = YoutubeRss.get_recent_media_ids_from_rss(source)
end
test "calls the expected URL for playlist sources" do
source = source_fixture(collection_type: :playlist, collection_id: "playlist_id")
expect(HTTPClientMock, :get, fn url ->
assert url =~ "https://www.youtube.com/feeds/videos.xml?playlist_id=#{source.collection_id}"
{:ok, ""}
end)
assert {:ok, _} = YoutubeRss.get_recent_media_ids_from_rss(source)
end
test "returns an error if the HTTP request fails", %{source: source} do
expect(HTTPClientMock, :get, fn _url -> {:error, ""} end)
assert {:error, "Failed to fetch RSS feed"} = YoutubeRss.get_recent_media_ids_from_rss(source)
end
test "returns the media IDs from the RSS feed", %{source: source} do
expect(HTTPClientMock, :get, fn _url ->
{:ok, "<yt:videoId>test_1</yt:videoId><yt:videoId>test_2</yt:videoId>"}
end)
assert {:ok, ["test_1", "test_2"]} = YoutubeRss.get_recent_media_ids_from_rss(source)
end
test "strips whitespace from media IDs", %{source: source} do
expect(HTTPClientMock, :get, fn _url ->
{:ok, "<yt:videoId> test_1 </yt:videoId><yt:videoId> test_2 </yt:videoId>"}
end)
assert {:ok, ["test_1", "test_2"]} = YoutubeRss.get_recent_media_ids_from_rss(source)
end
test "removes empty media IDs", %{source: source} do
expect(HTTPClientMock, :get, fn _url ->
{:ok, "<yt:videoId>test_1</yt:videoId><yt:videoId></yt:videoId>"}
end)
assert {:ok, ["test_1"]} = YoutubeRss.get_recent_media_ids_from_rss(source)
end
end
end