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.Tasks.Task
alias Pinchflat.Sources.Source
alias Pinchflat.Media.MediaItem
alias Pinchflat.Tasks.SourceTasks
alias Pinchflat.Media.MediaMetadata
alias Pinchflat.Sources.Source
alias Pinchflat.Profiles.MediaProfile
alias Pinchflat.Tasks
@ -13,7 +13,11 @@ alias Pinchflat.Profiles
alias Pinchflat.Sources
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.Utils.FilesystemUtils.FileFollowerServer
@ -38,7 +42,7 @@ defmodule IexHelpers do
:channel -> channel_url()
end
SourceDetails.get_source_details(source)
YtDlpCollection.get_source_details(source)
end
def ids(type) do
@ -48,7 +52,7 @@ defmodule IexHelpers do
:channel -> channel_url()
end
SourceDetails.get_media_attributes_for_collection(source)
YtDlpCollection.get_media_attributes_for_collection(source)
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
"""
@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()}
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