diff --git a/apps/bright/lib/bright/oban_workers/process_posts.ex b/apps/bright/lib/bright/oban_workers/process_posts.ex
index 0afae66..69f5ad8 100644
--- a/apps/bright/lib/bright/oban_workers/process_posts.ex
+++ b/apps/bright/lib/bright/oban_workers/process_posts.ex
@@ -10,6 +10,7 @@ defmodule Bright.ObanWorkers.ProcessPosts do
alias Bright.Socials.XPost
alias Bright.Streams.Stream
alias Bright.Platforms.Platform
+ alias Bright.Platforms
import Ecto.Query
require Logger
@@ -23,10 +24,10 @@ defmodule Bright.ObanWorkers.ProcessPosts do
def perform(%Oban.Job{args: %{}}) do
Logger.info(">>>> Process Posts is performing.")
- known_platforms = Repo.all(Platform)
+ known_platforms = Platform |> Repo.all() |> Repo.preload(:platform_aliases)
{num, nil} =
- get_unprocessed_posts()
+ XPost.get_unprocessed_posts()
|> then(fn posts ->
if posts == [] do
Logger.info("No unprocessed posts found")
@@ -42,19 +43,17 @@ defmodule Bright.ObanWorkers.ProcessPosts do
def process_post(post, known_platforms) do
with platforms <- XPost.get_platforms_mentioned(post, known_platforms),
- true <- is_nsfw_live_annoucement?(post, platforms, known_platforms),
+ true <- XPost.is_nsfw_live_announcement?(post, platforms, known_platforms),
{:ok, _stream} <- create_stream(post, platforms) do
:ok
else
- _ -> :ok
- end
- end
+ idk ->
+ Logger.debug(
+ "process_post did not find a nsfw live announcement. post=#{inspect(post)} known_platforms=#{inspect(known_platforms)}"
+ )
- def get_unprocessed_posts() do
- XPost
- |> where([p], is_nil(p.processed_at))
- |> preload(:vtuber)
- |> Repo.all()
+ :ok
+ end
end
@doc """
@@ -99,43 +98,4 @@ defmodule Bright.ObanWorkers.ProcessPosts do
# No posts to update
defp mark_posts_as_processed(_), do: :ok
-
- @doc """
- Is the post a valid NSFW livestream announcement?
-
- Eligibility requirements.
- To be considered a NSFW live announcement, a post must satisfy all the following conditions.
-
- * The post is authored by the lewdtuber
- * The post mentions a NSFW platform
- * The post does not contain any URLs to SFW streaming platforms.
-
- """
- def is_nsfw_live_annoucement?(%XPost{vtuber: vtuber} = post, platforms, known_platforms) do
- Logger.debug("Checking if post is NSFW live announcement: #{inspect(post)}")
-
- nsfw_platforms = Enum.filter(known_platforms, & &1.nsfw?)
- sfw_platforms = Enum.reject(known_platforms, & &1.nsfw?)
-
- conditions = [
- {:authored_by_vtuber?, not is_nil(vtuber)},
- {:contains_nsfw_link?,
- Enum.any?(platforms, fn plat -> Enum.any?(nsfw_platforms, &match_platform?(plat, &1)) end)},
- {:no_sfw_link?,
- not Enum.any?(platforms, fn plat ->
- Enum.any?(sfw_platforms, &match_platform?(plat, &1))
- end)}
- ]
-
- Enum.reduce_while(conditions, true, fn {label, condition}, _acc ->
- if condition do
- {:cont, true}
- else
- Logger.debug("NSFW announcement check failed at: #{label}")
- {:halt, false}
- end
- end)
- end
-
- defp match_platform?(plat, platform), do: String.contains?(plat, &URI.parse(&1.url).hostname)
end
diff --git a/apps/bright/lib/bright/platforms.ex b/apps/bright/lib/bright/platforms.ex
index 14f95a4..ed5295c 100644
--- a/apps/bright/lib/bright/platforms.ex
+++ b/apps/bright/lib/bright/platforms.ex
@@ -38,8 +38,8 @@ defmodule Bright.Platforms do
"""
def get_platform!(id) do
Platform
- |> Repo.get!(Platform, id)
- |> Repo.preload([:platform_aliases])
+ |> Repo.get!(id)
+ |> Repo.preload(:platform_aliases)
end
@doc """
@@ -125,4 +125,15 @@ defmodule Bright.Platforms do
|> PlatformAlias.changeset(attrs)
|> Repo.insert()
end
+
+ def match_platform?(a, b) do
+ URI.parse(a.url).host === URI.parse(b.url).host
+ end
+
+ @doc """
+ Do any of the A platforms match any of the B platforms?
+ """
+ def contains_platform?(a, b) do
+ Enum.any?(a, fn plat -> Enum.any?(b, &match_platform?(plat, &1)) end)
+ end
end
diff --git a/apps/bright/lib/bright/platforms/platform.ex b/apps/bright/lib/bright/platforms/platform.ex
index 191efe2..ec2aa62 100644
--- a/apps/bright/lib/bright/platforms/platform.ex
+++ b/apps/bright/lib/bright/platforms/platform.ex
@@ -18,5 +18,6 @@ defmodule Bright.Platforms.Platform do
platform
|> cast(attrs, [:name, :url, :slug])
|> validate_required([:name, :url, :slug])
+ |> unique_constraint(:name)
end
end
diff --git a/apps/bright/lib/bright/socials.ex b/apps/bright/lib/bright/socials.ex
index c0aef76..a2fbb3b 100644
--- a/apps/bright/lib/bright/socials.ex
+++ b/apps/bright/lib/bright/socials.ex
@@ -1,7 +1,26 @@
defmodule Bright.Socials do
+ alias Bright.Socials.XPost
+ alias Bright.Repo
+
@moduledoc """
Socials context, for functions for interacting with social media platforms
"""
+ @doc """
+ Creates a x_post.
+ ## Examples
+
+ iex> x_post(%{field: value})
+ {:ok, %XPost{}}
+
+ iex> x_post(%{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def create_x_post(attrs \\ %{}) do
+ %XPost{}
+ |> XPost.changeset(attrs)
+ |> Repo.insert()
+ end
end
diff --git a/apps/bright/lib/bright/socials/x_post.ex b/apps/bright/lib/bright/socials/x_post.ex
index 70f9245..1ec9d5f 100644
--- a/apps/bright/lib/bright/socials/x_post.ex
+++ b/apps/bright/lib/bright/socials/x_post.ex
@@ -5,6 +5,7 @@ defmodule Bright.Socials.XPost do
alias Bright.Vtubers.Vtuber
alias Bright.Socials.{XPost, RSSParser}
alias Bright.Platforms.Platform
+ alias Bright.Platforms
alias Quinn
require Logger
@@ -66,6 +67,17 @@ defmodule Bright.Socials.XPost do
end
end
+ def authored_by_vtuber?(x_post, vtuber) do
+ x_post.vtuber_id === vtuber.id
+ end
+
+ def get_unprocessed_posts() do
+ XPost
+ |> where([p], is_nil(p.processed_at))
+ |> Repo.all()
+ |> Repo.preload(:vtuber)
+ end
+
def extract_hostname(url) do
uri = URI.parse(url)
uri.host || ""
@@ -113,6 +125,45 @@ defmodule Bright.Socials.XPost do
def get_platforms_mentioned(raw_text, platforms) do
Enum.filter(platforms, &includes_platform?(raw_text, &1))
end
+
+ @doc """
+ Is the post a valid NSFW livestream announcement?
+
+ Eligibility requirements.
+ To be considered a NSFW live announcement, a post must satisfy all the following conditions.
+
+ * The post is authored by the lewdtuber
+ * The post does not contain, "VOD/i"
+ * The post mentions a NSFW platform
+ * The post does not mention any SFW streaming platform.
+
+ """
+ def is_nsfw_live_announcement?(
+ %XPost{vtuber: vtuber} = post,
+ mentioned_platforms,
+ known_platforms
+ ) do
+ Logger.debug("Checking if post is NSFW live announcement: #{inspect(post)}")
+
+ nsfw_platforms = Enum.filter(known_platforms, & &1.nsfw)
+ sfw_platforms = Enum.reject(known_platforms, & &1.nsfw)
+
+ conditions = [
+ # {:authored_by_vtuber?, XPost.authored_by_vtuber?(post, vtuber)}, # This one might not make sense. I think we only get posts from the vtuber's feed
+ {:not_vod?, not String.contains?(String.downcase(post.raw), "vod")},
+ {:contains_nsfw_link?, Platforms.contains_platform?(mentioned_platforms, nsfw_platforms)},
+ {:no_sfw_link?, not Platforms.contains_platform?(mentioned_platforms, sfw_platforms)}
+ ]
+
+ Enum.reduce_while(conditions, true, fn {label, condition}, _acc ->
+ if condition do
+ {:cont, true}
+ else
+ Logger.debug("NSFW announcement check failed at: #{label}")
+ {:halt, false}
+ end
+ end)
+ end
end
defimpl Phoenix.HTML.Safe, for: Bright.Socials.XPost do
diff --git a/apps/bright/test/bright/oban_workers/process_posts_test.exs b/apps/bright/test/bright/oban_workers/process_posts_test.exs
index a8f619b..de6484b 100644
--- a/apps/bright/test/bright/oban_workers/process_posts_test.exs
+++ b/apps/bright/test/bright/oban_workers/process_posts_test.exs
@@ -7,47 +7,82 @@ defmodule Bright.ProcessPostsTest do
alias Bright.Streams
alias Bright.Streams.Stream
alias Bright.Platforms.Platform
+ alias Bright.Platforms
alias Bright.VtubersFixtures
alias Bright.Socials.XPost
+ alias Bright.Socials
setup do
vtuber = Bright.VtubersFixtures.vtuber_fixture()
- %Platform{
+ Platforms.create_platform(%{
name: "Fansly",
slug: "fansly",
url: "https://fansly.com",
nsfw: true
- }
- |> Repo.insert!()
+ })
- %Platform{
+ Platforms.create_platform(%{
name: "Twitch",
slug: "twitch",
url: "https://twitch.tv",
nsfw: false
- }
- |> Repo.insert!()
+ })
- %Platform{
+ Platforms.create_platform(%{
name: "OnlyFans",
slug: "onlyfans",
url: "https://onlyfans.com",
nsfw: true
- }
- |> Repo.insert!()
+ })
- posts =
- for i <- 1..3 do
- %XPost{
- raw: "Raw content #{i}",
- url: "https://example.com/post#{i}",
- date: DateTime.utc_now(:second),
- processed_at: nil,
- vtuber_id: vtuber.id
- }
- |> Repo.insert!()
- end
+ Platforms.create_platform(%{
+ name: "Chaturbate",
+ slug: "chaturbate",
+ url: "https://chaturbate.com",
+ nsfw: true
+ })
+
+ posts = [
+ # these posts are valid nsfw livestream announcements
+ Socials.create_x_post(%{
+ raw: "I'm going live! fansly.com/fakename <3",
+ url: "https://x.com/fakename/status/283498235",
+ date: DateTime.utc_now(:second),
+ processed_at: nil,
+ vtuber_id: vtuber.id
+ }),
+ Socials.create_x_post(%{
+ raw: "gm! tiem for sex breakfast https://onlyfans.com/fakename",
+ url: "https://x.com/fakename/status/283498234",
+ date: DateTime.utc_now(:second),
+ processed_at: nil,
+ vtuber_id: vtuber.id
+ }),
+ Socials.create_x_post(%{
+ raw: "ero strim rn http://chaturbate.com/fakename",
+ url: "https://x.com/fakename/status/283498232",
+ date: DateTime.utc_now(:second),
+ processed_at: nil,
+ vtuber_id: vtuber.id
+ }),
+ # these posts are NOT valid livestream invitations
+ Socials.create_x_post(%{
+ raw: "Let's play a game http://twitch.tv/fakename",
+ url: "https://x.com/fakename/status/283498343",
+ date: DateTime.utc_now(:second),
+ processed_at: nil,
+ vtuber_id: vtuber.id
+ }),
+ Socials.create_x_post(%{
+ raw:
+ "Be sure to follow me on my socials http://chaturbate.com/fakename http://twitch.tv/fakename http://onlyfans.com/fakename http://linktree.com/fakename",
+ url: "https://x.com/fakename/status/283498349",
+ date: DateTime.utc_now(:second),
+ processed_at: nil,
+ vtuber_id: vtuber.id
+ })
+ ]
{:ok, posts: posts, vtuber: vtuber}
end
diff --git a/apps/bright/test/bright/platforms_test.exs b/apps/bright/test/bright/platforms_test.exs
index 18d4ef5..616b586 100644
--- a/apps/bright/test/bright/platforms_test.exs
+++ b/apps/bright/test/bright/platforms_test.exs
@@ -2,31 +2,29 @@ defmodule Bright.PlatformsTest do
use Bright.DataCase
alias Bright.Platforms
+ alias Bright.Platforms.Platform
describe "platforms" do
- alias Bright.Platforms.Platform
-
import Bright.PlatformsFixtures
- @invalid_attrs %{name: 7, url: 7, icon: 7}
+ @invalid_attrs %{name: 7, url: 7}
test "list_platforms/0 returns all platforms" do
platform = platform_fixture()
- assert Platforms.list_platforms() == [platform]
+ assert Platforms.list_platforms() |> length === 1
end
test "get_platform!/1 returns the platform with given id" do
- platform = platform_fixture()
- assert Platforms.get_platform!(platform.id) == platform
+ platform = platform_fixture(%{name: "Chaturbate"})
+ assert Platforms.get_platform!(platform.id).name == platform.name
end
test "create_platform/1 with valid data creates a platform" do
- valid_attrs = %{name: "some name", url: "some url", icon: ""}
+ valid_attrs = %{name: "some name", url: "some url", slug: "some_slug"}
assert {:ok, %Platform{} = platform} = Platforms.create_platform(valid_attrs)
assert platform.name == "some name"
assert platform.url == "some url"
- assert platform.icon == ""
end
test "create_platform/1 with invalid data returns error changeset" do
@@ -38,20 +36,17 @@ defmodule Bright.PlatformsTest do
update_attrs = %{
name: "some updated name",
- url: "https://example.com",
- icon: ""
+ url: "https://example.com"
}
assert {:ok, %Platform{} = platform} = Platforms.update_platform(platform, update_attrs)
assert platform.name == "some updated name"
assert platform.url == "https://example.com"
- assert platform.icon == ""
end
test "update_platform/2 with invalid data returns error changeset" do
platform = platform_fixture()
- assert {:error, %Ecto.Changeset{}} = Platforms.update_platform(platform, @invalid_attrs)
- assert platform == Platforms.get_platform!(platform.id)
+ assert {:error, %Ecto.Changeset{}} = platform |> Platforms.update_platform(@invalid_attrs)
end
test "delete_platform/1 deletes the platform" do
@@ -65,4 +60,39 @@ defmodule Bright.PlatformsTest do
assert %Ecto.Changeset{} = Platforms.change_platform(platform)
end
end
+
+ describe "match_platform?/2" do
+ test "compares a platform with another and returns true if the two are the same platform" do
+ platformA = %Platform{name: "Twitch", url: "https://twitch.tv"}
+ platformB = %Platform{name: "Chaturbate", url: "https://chaturbate.com"}
+ platformC = %Platform{name: "Twitch", url: "https://twitch.tv"}
+ assert Platforms.match_platform?(platformA, platformC)
+ assert not Platforms.match_platform?(platformA, platformB)
+ end
+ end
+
+ describe "contains_platform?/2" do
+ @tag :unit
+ test "accepts a list of platforms and returns true if one of them match any of a list of given platform" do
+ platformA = %Platform{name: "Twitch", url: "https://twitch.tv"}
+ platformB = %Platform{name: "Chaturbate", url: "https://chaturbate.com"}
+ platformC = %Platform{name: "Twitch", url: "https://twitch.tv"}
+ assert Platforms.contains_platform?([platformA], [platformB, platformC])
+ assert not Platforms.contains_platform?([platformA], [platformB])
+ end
+
+ @tag :unit
+ test "returns true if configured to return true for SFW platforms" do
+ platformA = %Platform{name: "Twitch", url: "https://twitch.tv"}
+ platformB = %Platform{name: "YouTube", url: "https://youtube.com"}
+ assert Platforms.contains_platform?([platformA], [platformB, platformA])
+ end
+
+ @tag :unit
+ test "returns false if matching against an empty list" do
+ platformA = %Platform{name: "Twitch", url: "https://twitch.tv"}
+ assert not Platforms.contains_platform?([platformA], [])
+ assert not Platforms.contains_platform?([], [platformA])
+ end
+ end
end
diff --git a/apps/bright/test/bright/socials/x_post_test.exs b/apps/bright/test/bright/socials/x_post_test.exs
index cb6ec4e..4767a15 100644
--- a/apps/bright/test/bright/socials/x_post_test.exs
+++ b/apps/bright/test/bright/socials/x_post_test.exs
@@ -2,8 +2,10 @@ defmodule Bright.XPostTest do
use Bright.DataCase
alias Bright.Socials.XPost
+ alias Bright.Socials
+ alias Bright.Vtubers
alias Bright.Vtubers.Vtuber
- alias Bright.XPostsFixtures
+ alias Bright.{SocialsFixtures, VtubersFixtures, XPostsFixtures, PlatformsFixtures}
alias Bright.Platforms.{Platform, PlatformAlias}
alias Bright.Platforms
alias Bright.VultrAI
@@ -11,6 +13,31 @@ defmodule Bright.XPostTest do
@sample_feed "https://rss.app/feeds/FhPetvUY036xiFau.xml"
+ describe "authored_by_vtuber?/2" do
+ @tag :unit
+ test "returns true when the given post is authored by the given vtuber" do
+ vtuber = VtubersFixtures.vtuber_fixture(%{name: "ProjektMelody", slug: "projektmelody"})
+ x_post = SocialsFixtures.x_post_fixture(%{vtuber_id: vtuber.id})
+ assert XPost.authored_by_vtuber?(x_post, vtuber)
+ end
+
+ @tag :unit
+ test "returns false when the given post is NOT authored by the given vtuber" do
+ vtuber = VtubersFixtures.vtuber_fixture(%{name: "ProjektMelody", slug: "projektmelody"})
+ vtuberB = VtubersFixtures.vtuber_fixture(%{name: "Vex", slug: "vex"})
+
+ {:ok, x_post} =
+ Socials.create_x_post(%{
+ raw: "test",
+ url: "https://x.com/projektmelody/status/1234",
+ date: DateTime.utc_now(:second),
+ vtuber_id: vtuber.id
+ })
+
+ assert not XPost.authored_by_vtuber?(x_post, vtuberB)
+ end
+ end
+
describe "get_new_posts" do
@tag :integration
test "get_new_posts/1 with URL" do
@@ -35,6 +62,36 @@ defmodule Bright.XPostTest do
end
end
+ describe "get_unprocessed_posts/0" do
+ setup do
+ vtuber =
+ %Vtuber{
+ display_name: "Some Vtuber",
+ slug: "some-vtuber"
+ }
+ |> Repo.insert!()
+
+ posts =
+ for i <- 1..3 do
+ %XPost{
+ raw: "Raw content #{i}",
+ url: "https://example.com/post#{i}",
+ date: DateTime.utc_now(:second),
+ processed_at: nil,
+ vtuber_id: vtuber.id
+ }
+ |> Repo.insert!()
+ end
+
+ {:ok, posts: posts}
+ end
+
+ @tag :unit
+ test "gets posts with nil processed_at" do
+ assert length(XPost.get_unprocessed_posts()) == 3
+ end
+ end
+
describe "includes_alias?/2" do
setup do
ytmnd =
@@ -289,5 +346,100 @@ defmodule Bright.XPostTest do
assert Enum.sort(actual_platform_names) == Enum.sort(expected_platform_names)
end
+
+ @tag :unit
+ test "post with a platform alias" do
+ known_platforms = Platforms.list_platforms()
+ expected_platform_names = ["Fansly"]
+
+ actual_platform_names =
+ XPost.get_platforms_mentioned(
+ XPostsFixtures.fixture_live_4() |> Map.get(:raw),
+ known_platforms
+ )
+ |> Enum.map(& &1.name)
+
+ assert actual_platform_names == expected_platform_names
+ end
+ end
+
+ describe "is_nsfw_live_announcement?/3" do
+ setup do
+ vtuber = VtubersFixtures.vtuber_fixture()
+
+ {:ok, x_post} =
+ Socials.create_x_post(%{
+ raw: "I'm going live https://twitch.tv/bigchungus",
+ url: "https://x.com/bigchungus/status/1234",
+ date: DateTime.utc_now(:second),
+ vtuber_id: vtuber.id
+ })
+
+ x_post = Repo.preload(x_post, :vtuber)
+ known_platforms = PlatformsFixtures.known_platforms_fixture()
+
+ mentioned_platforms = [
+ PlatformsFixtures.onlyfans_fixture(),
+ PlatformsFixtures.chaturbate_fixture(),
+ PlatformsFixtures.fansly_fixture()
+ ]
+
+ {:ok,
+ vtuber: vtuber,
+ x_post: x_post,
+ known_platforms: known_platforms,
+ mentioned_platforms: mentioned_platforms}
+ end
+
+ @tag :integration
+ test "should return false when receiving a XPost linking to a SFW platform", %{
+ vtuber: vtuber,
+ x_post: x_post,
+ known_platforms: known_platforms
+ } do
+ mentioned_platforms = [
+ PlatformsFixtures.twitch_fixture(),
+ PlatformsFixtures.fansly_fixture(),
+ PlatformsFixtures.onlyfans_fixture(),
+ PlatformsFixtures.chaturbate_fixture()
+ ]
+
+ assert not XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
+ end
+
+ test "should return true when receiving an XPost with only Chaturbate mentioned", %{
+ vtuber: vtuber,
+ x_post: x_post,
+ known_platforms: known_platforms,
+ mentioned_platforms: mentioned_platforms
+ } do
+ mentioned_platforms = [
+ PlatformsFixtures.chaturbate_fixture()
+ ]
+
+ assert XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
+ end
+
+ test "should return true when receiving an XPost with only NSFW platforms mentioned", %{
+ vtuber: vtuber,
+ x_post: x_post,
+ known_platforms: known_platforms,
+ mentioned_platforms: mentioned_platforms
+ } do
+ assert XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
+ end
+
+ test "should return false when receiving an XPost with vod/i", %{
+ vtuber: vtuber,
+ known_platforms: known_platforms
+ } do
+ x_post = %XPost{
+ raw:
+ "IRL JOI handcam stream! Listen to my instructions and stroke your cock for me until you cum šš¦\n\nThe rest of the VOD is available here for Tier 1 subscribers or for $10! š\nā¶ļø https://fansly.com/post/755934614"
+ }
+
+ mentioned_platforms = [PlatformsFixtures.fansly_fixture()]
+ assert not XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
+ end
end
end
diff --git a/apps/bright/test/support/fixtures/platforms_fixtures.ex b/apps/bright/test/support/fixtures/platforms_fixtures.ex
index 1d991cf..9a1d8ab 100644
--- a/apps/bright/test/support/fixtures/platforms_fixtures.ex
+++ b/apps/bright/test/support/fixtures/platforms_fixtures.ex
@@ -4,16 +4,16 @@ defmodule Bright.PlatformsFixtures do
entities via the `Bright.Platforms` context.
"""
- def platform_fixture(attrs \\ %{})
+ alias Bright.Platforms.Platform
@doc """
Generate a platform.
"""
- def platform_fixture(attrs) do
+ def platform_fixture(attrs \\ %{}) do
{:ok, platform} =
attrs
|> Enum.into(%{
- icon: "some icon",
+ slug: "some_slug",
name: "some name",
url: "some url"
})
@@ -21,4 +21,49 @@ defmodule Bright.PlatformsFixtures do
platform
end
+
+ def known_platforms_fixture() do
+ [
+ twitch_fixture(),
+ fansly_fixture(),
+ chaturbate_fixture(),
+ onlyfans_fixture(),
+ linktree_fixture(),
+ discord_fixture(),
+ carrd_fixture(),
+ throne_fixture()
+ ]
+ end
+
+ def twitch_fixture() do
+ %Platform{name: "Twitch", slug: "twitch", url: "https://twitch.tv", nsfw: false}
+ end
+
+ def fansly_fixture() do
+ %Platform{name: "Fansly", slug: "fansly", url: "https://fansly.com", nsfw: true}
+ end
+
+ def chaturbate_fixture() do
+ %Platform{name: "Chaturbate", slug: "chaturbate", url: "https://chaturbate.com", nsfw: true}
+ end
+
+ def onlyfans_fixture() do
+ %Platform{name: "OnlyFans", slug: "onlyfans", url: "https://onlyfans.com", nsfw: true}
+ end
+
+ def linktree_fixture() do
+ %Platform{name: "Linktree", slug: "linktree", url: "https://linktr.ee", nsfw: false}
+ end
+
+ def discord_fixture() do
+ %Platform{name: "Discord", slug: "discord", url: "https://discord.com", nsfw: false}
+ end
+
+ def carrd_fixture() do
+ %Platform{name: "Carrd", slug: "carrd", url: "https://carrd.co", nsfw: false}
+ end
+
+ def throne_fixture() do
+ %Platform{name: "Throne", slug: "throne", url: "https://throne.com", nsfw: false}
+ end
end
diff --git a/apps/bright/test/support/fixtures/socials_fixtures.ex b/apps/bright/test/support/fixtures/socials_fixtures.ex
new file mode 100644
index 0000000..d95a0f7
--- /dev/null
+++ b/apps/bright/test/support/fixtures/socials_fixtures.ex
@@ -0,0 +1,23 @@
+defmodule Bright.SocialsFixtures do
+ @moduledoc """
+ This module defines test helpers for creating
+ entities via the `Bright.Socials` context.
+ """
+
+ @doc """
+ Generate a platform.
+ """
+ def x_post_fixture(attrs \\ %{}) do
+ {:ok, x_post} =
+ attrs
+ |> Enum.into(%{
+ raw: "some raw text",
+ url: "https://x.com/fakeuser/status/9876",
+ processed_at: nil,
+ date: DateTime.utc_now(:second)
+ })
+ |> Bright.Socials.create_x_post()
+
+ x_post
+ end
+end
diff --git a/apps/bright/test/support/fixtures/x_posts_fixtures.ex b/apps/bright/test/support/fixtures/x_posts_fixtures.ex
index 6a7a374..c8efc3d 100644
--- a/apps/bright/test/support/fixtures/x_posts_fixtures.ex
+++ b/apps/bright/test/support/fixtures/x_posts_fixtures.ex
@@ -82,6 +82,14 @@ defmodule Bright.XPostsFixtures do
}
end
+ def fixture_live_4() do
+ %XPost{
+ raw: "http://melody.buzz",
+ date: ~U[2025-05-05T05:05:05.000Z],
+ url: "https://x.com/ProjektMelody/status/5555"
+ }
+ end
+
@doc """
Generates a basic x_post fixture.
"""