mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
add livestream tracking
This commit is contained in:
parent
4796d3d628
commit
1e1b0054d0
11 changed files with 253 additions and 63 deletions
|
@ -18,6 +18,7 @@ config :philomena,
|
|||
advert_url_root: "/spns",
|
||||
badge_url_root: "/media",
|
||||
tag_url_root: "/media",
|
||||
channel_url_root: "/media",
|
||||
image_file_root: "priv/static/system/images",
|
||||
cdn_host: "",
|
||||
proxy_host: nil,
|
||||
|
|
|
@ -16,6 +16,7 @@ config :bcrypt_elixir,
|
|||
|
||||
config :philomena,
|
||||
anonymous_name_salt: System.get_env("ANONYMOUS_NAME_SALT"),
|
||||
channel_url_root: System.get_env("CHANNEL_URL_ROOT"),
|
||||
password_pepper: System.get_env("PASSWORD_PEPPER"),
|
||||
avatar_url_root: System.get_env("AVATAR_URL_ROOT"),
|
||||
advert_url_root: System.get_env("ADVERT_URL_ROOT"),
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Philomena.Channels do
|
|||
alias Philomena.Repo
|
||||
|
||||
alias Philomena.Channels.Channel
|
||||
alias Philomena.Notifications
|
||||
|
||||
@doc """
|
||||
Returns the list of channels.
|
||||
|
@ -104,35 +105,6 @@ defmodule Philomena.Channels do
|
|||
|
||||
alias Philomena.Channels.Subscription
|
||||
|
||||
@doc """
|
||||
Returns the list of channel_subscriptions.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_channel_subscriptions()
|
||||
[%Subscription{}, ...]
|
||||
|
||||
"""
|
||||
def list_channel_subscriptions do
|
||||
Repo.all(Subscription)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single subscription.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Subscription does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_subscription!(123)
|
||||
%Subscription{}
|
||||
|
||||
iex> get_subscription!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_subscription!(id), do: Repo.get!(Subscription, id)
|
||||
|
||||
@doc """
|
||||
Creates a subscription.
|
||||
|
||||
|
@ -145,28 +117,11 @@ defmodule Philomena.Channels do
|
|||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_subscription(attrs \\ %{}) do
|
||||
%Subscription{}
|
||||
|> Subscription.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a subscription.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_subscription(subscription, %{field: new_value})
|
||||
{:ok, %Subscription{}}
|
||||
|
||||
iex> update_subscription(subscription, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_subscription(%Subscription{} = subscription, attrs) do
|
||||
subscription
|
||||
|> Subscription.changeset(attrs)
|
||||
|> Repo.update()
|
||||
def create_subscription(_channel, nil), do: {:ok, nil}
|
||||
def create_subscription(channel, user) do
|
||||
%Subscription{channel_id: channel.id, user_id: user.id}
|
||||
|> Subscription.changeset(%{})
|
||||
|> Repo.insert(on_conflict: :nothing)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -181,20 +136,22 @@ defmodule Philomena.Channels do
|
|||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_subscription(%Subscription{} = subscription) do
|
||||
Repo.delete(subscription)
|
||||
def delete_subscription(channel, user) do
|
||||
clear_notification(channel, user)
|
||||
|
||||
%Subscription{channel_id: channel.id, user_id: user.id}
|
||||
|> Repo.delete()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking subscription changes.
|
||||
def subscribed?(_channel, nil), do: false
|
||||
def subscribed?(channel, user) do
|
||||
Subscription
|
||||
|> where(channel_id: ^channel.id, user_id: ^user.id)
|
||||
|> Repo.exists?()
|
||||
end
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_subscription(subscription)
|
||||
%Ecto.Changeset{source: %Subscription{}}
|
||||
|
||||
"""
|
||||
def change_subscription(%Subscription{} = subscription) do
|
||||
Subscription.changeset(subscription, %{})
|
||||
def clear_notification(channel, user) do
|
||||
Notifications.delete_unread_notification("Channel", channel.id, user)
|
||||
Notifications.delete_unread_notification("LivestreamChannel", channel.id, user)
|
||||
end
|
||||
end
|
||||
|
|
30
lib/philomena_web/controllers/channel_controller.ex
Normal file
30
lib/philomena_web/controllers/channel_controller.ex
Normal file
|
@ -0,0 +1,30 @@
|
|||
defmodule PhilomenaWeb.ChannelController do
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
alias Philomena.Channels
|
||||
alias Philomena.Channels.Channel
|
||||
alias Philomena.Repo
|
||||
import Ecto.Query
|
||||
|
||||
plug :load_resource, model: Channel
|
||||
|
||||
def index(conn, _params) do
|
||||
channels =
|
||||
Channel
|
||||
|> where([c], c.nsfw == false and not is_nil(c.last_fetched_at))
|
||||
|> order_by(desc: :is_live, asc: :title)
|
||||
|> preload(:associated_artist_tag)
|
||||
|> Repo.paginate(conn.assigns.scrivener)
|
||||
|
||||
render(conn, "index.html", layout_class: "layout--wide", channels: channels)
|
||||
end
|
||||
|
||||
def show(conn, _params) do
|
||||
channel = conn.assigns.channel
|
||||
user = conn.assigns.current_user
|
||||
|
||||
if user, do: Channels.clear_notification(channel, user)
|
||||
|
||||
redirect(conn, external: channel.url)
|
||||
end
|
||||
end
|
18
lib/philomena_web/plugs/channel_plug.ex
Normal file
18
lib/philomena_web/plugs/channel_plug.ex
Normal file
|
@ -0,0 +1,18 @@
|
|||
defmodule PhilomenaWeb.ChannelPlug do
|
||||
alias Plug.Conn
|
||||
alias Philomena.Channels.Channel
|
||||
alias Philomena.Repo
|
||||
import Ecto.Query
|
||||
|
||||
def init([]), do: []
|
||||
|
||||
def call(conn, _opts) do
|
||||
live_channels =
|
||||
Channel
|
||||
|> where(is_live: true)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
|
||||
conn
|
||||
|> Conn.assign(:live_channels, live_channels)
|
||||
end
|
||||
end
|
|
@ -18,6 +18,7 @@ defmodule PhilomenaWeb.Router do
|
|||
plug PhilomenaWeb.SiteNoticePlug
|
||||
plug PhilomenaWeb.ForumListPlug
|
||||
plug PhilomenaWeb.FilterSelectPlug
|
||||
plug PhilomenaWeb.ChannelPlug
|
||||
end
|
||||
|
||||
pipeline :api do
|
||||
|
@ -134,6 +135,7 @@ defmodule PhilomenaWeb.Router do
|
|||
resources "/dnp", DnpEntryController, only: [:index, :show]
|
||||
resources "/staff", StaffController, only: [:index]
|
||||
resources "/stats", StatController, only: [:index]
|
||||
resources "/channels", ChannelController, only: [:index, :show]
|
||||
|
||||
get "/:id", ImageController, :show
|
||||
# get "/:forum_id", ForumController, :show # impossible to do without constraints
|
||||
|
|
34
lib/philomena_web/templates/channel/_channel_box.html.slime
Normal file
34
lib/philomena_web/templates/channel/_channel_box.html.slime
Normal file
|
@ -0,0 +1,34 @@
|
|||
- link_class = "media-box__header media-box__header--channel media-box__header--link"
|
||||
|
||||
.media-box
|
||||
a.media-box__header.media-box__header--channel.media-box__header--link href=Routes.channel_path(@conn, :show, @channel) title=@channel.title
|
||||
= @channel.title || @channel.short_name
|
||||
|
||||
.media-box__header.media-box__header--channel
|
||||
= if @channel.is_live do
|
||||
.spacing-right.label.label--success.label--block.label--small: strong LIVE NOW
|
||||
=> @channel.viewers
|
||||
=> pluralize "viewer", "viewers", @channel.viewers
|
||||
- else
|
||||
.label.label--danger.label--block.label--small: strong OFF AIR
|
||||
|
||||
= if @channel.nsfw do
|
||||
.media-box__overlay
|
||||
| NSFW
|
||||
|
||||
.media-box__content.media-box__content--channel
|
||||
a href=Routes.channel_path(@conn, :show, @channel)
|
||||
.image-constrained.media-box__content--channel
|
||||
img src=channel_image(@channel) alt="#{@channel.title}"
|
||||
|
||||
= if @channel.associated_artist_tag do
|
||||
a href=Routes.tag_path(@conn, :show, @channel.associated_artist_tag) class=link_class
|
||||
i.fa.fa-fw.fa-tags>
|
||||
= @channel.associated_artist_tag.name
|
||||
- else
|
||||
.media-box__header.media-box__header--channel No artist tag
|
||||
|
||||
/- if current_user
|
||||
/ = subscription_link channel, current_user, class: link_class
|
||||
/- else
|
||||
/ .media-box__header.media-box__header--channel Login to subscribe
|
30
lib/philomena_web/templates/channel/index.html.slime
Normal file
30
lib/philomena_web/templates/channel/index.html.slime
Normal file
|
@ -0,0 +1,30 @@
|
|||
h1 Livestreams
|
||||
|
||||
- route = fn p -> Routes.channel_path(@conn, :index, p) end
|
||||
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @channels, route: route, conn: @conn
|
||||
|
||||
/= form_tag channels_path, method: :get, class: 'hform', enforce_utf8: false do
|
||||
.field
|
||||
= text_field_tag :cq, params[:cq], class: 'input hform__text', placeholder: 'Search channels'
|
||||
= submit_tag 'Search', class: 'hform__button button'
|
||||
.block
|
||||
.block__header
|
||||
= pagination
|
||||
|
||||
.block__content
|
||||
= for channel <- @channels do
|
||||
= render PhilomenaWeb.ChannelView, "_channel_box.html", channel: channel, conn: @conn
|
||||
|
||||
.block__header
|
||||
= pagination
|
||||
|
||||
br
|
||||
.clearfix
|
||||
h2 FAQ
|
||||
p
|
||||
strong> Q: Do you host streams?
|
||||
| A: No, we cheat and just link to streams on Livestream/Picarto since that's where (almost) everyone is already. This is simply a nice way to track streaming artists.
|
||||
p
|
||||
strong> Q: How do I get my stream/a friend's stream/<artist>'s stream here?
|
||||
' A: Send a private message to a site administrator
|
||||
' with a link to the stream and the artist tag if applicable.
|
|
@ -30,6 +30,11 @@
|
|||
| Post Search
|
||||
a.header__link href="/tags"
|
||||
| Tags
|
||||
a.header__link href="/channels"
|
||||
' Live
|
||||
span.header__counter
|
||||
= @conn.assigns.live_channels
|
||||
|
||||
a.header__link href='/galleries'
|
||||
| Galleries
|
||||
a.header__link href='/commissions'
|
||||
|
|
24
lib/philomena_web/views/channel_view.ex
Normal file
24
lib/philomena_web/views/channel_view.ex
Normal file
|
@ -0,0 +1,24 @@
|
|||
defmodule PhilomenaWeb.ChannelView do
|
||||
use PhilomenaWeb, :view
|
||||
|
||||
def channel_image(%{channel_image: image, is_live: false}) when image not in [nil, ""],
|
||||
do: channel_url_root() <> "/" <> image
|
||||
|
||||
def channel_image(%{type: "LivestreamChannel", short_name: short_name}) do
|
||||
now = DateTime.utc_now() |> DateTime.to_unix(:microsecond)
|
||||
Camo.Image.image_url("https://thumbnail.api.livestream.com/thumbnail?name=#{short_name}&rand=#{now}")
|
||||
end
|
||||
|
||||
def channel_image(%{type: "PicartoChannel", thumbnail_url: thumbnail_url}),
|
||||
do: Camo.Image.image_url(thumbnail_url || "https://picarto.tv/images/missingthumb.jpg")
|
||||
|
||||
def channel_image(%{type: "PiczelChannel", remote_stream_id: remote_stream_id}),
|
||||
do: Camo.Image.image_url("https://piczel.tv/api/thumbnail/stream_#{remote_stream_id}.jpg")
|
||||
|
||||
def channel_image(%{type: "TwitchChannel", short_name: short_name}),
|
||||
do: Camo.Image.image_url("https://static-cdn.jtvnw.net/previews-ttv/live_user_#{String.downcase(short_name)}-320x180.jpg")
|
||||
|
||||
defp channel_url_root do
|
||||
Application.get_env(:philomena, :channel_url_root)
|
||||
end
|
||||
end
|
88
test/philomena_web/controllers/channel_controller_test.exs
Normal file
88
test/philomena_web/controllers/channel_controller_test.exs
Normal file
|
@ -0,0 +1,88 @@
|
|||
defmodule PhilomenaWeb.ChannelControllerTest do
|
||||
use PhilomenaWeb.ConnCase
|
||||
|
||||
alias Philomena.Channels
|
||||
|
||||
@create_attrs %{}
|
||||
@update_attrs %{}
|
||||
@invalid_attrs %{}
|
||||
|
||||
def fixture(:channel) do
|
||||
{:ok, channel} = Channels.create_channel(@create_attrs)
|
||||
channel
|
||||
end
|
||||
|
||||
describe "index" do
|
||||
test "lists all channels", %{conn: conn} do
|
||||
conn = get(conn, Routes.channel_path(conn, :index))
|
||||
assert html_response(conn, 200) =~ "Listing Channels"
|
||||
end
|
||||
end
|
||||
|
||||
describe "new channel" do
|
||||
test "renders form", %{conn: conn} do
|
||||
conn = get(conn, Routes.channel_path(conn, :new))
|
||||
assert html_response(conn, 200) =~ "New Channel"
|
||||
end
|
||||
end
|
||||
|
||||
describe "create channel" do
|
||||
test "redirects to show when data is valid", %{conn: conn} do
|
||||
conn = post(conn, Routes.channel_path(conn, :create), channel: @create_attrs)
|
||||
|
||||
assert %{id: id} = redirected_params(conn)
|
||||
assert redirected_to(conn) == Routes.channel_path(conn, :show, id)
|
||||
|
||||
conn = get(conn, Routes.channel_path(conn, :show, id))
|
||||
assert html_response(conn, 200) =~ "Show Channel"
|
||||
end
|
||||
|
||||
test "renders errors when data is invalid", %{conn: conn} do
|
||||
conn = post(conn, Routes.channel_path(conn, :create), channel: @invalid_attrs)
|
||||
assert html_response(conn, 200) =~ "New Channel"
|
||||
end
|
||||
end
|
||||
|
||||
describe "edit channel" do
|
||||
setup [:create_channel]
|
||||
|
||||
test "renders form for editing chosen channel", %{conn: conn, channel: channel} do
|
||||
conn = get(conn, Routes.channel_path(conn, :edit, channel))
|
||||
assert html_response(conn, 200) =~ "Edit Channel"
|
||||
end
|
||||
end
|
||||
|
||||
describe "update channel" do
|
||||
setup [:create_channel]
|
||||
|
||||
test "redirects when data is valid", %{conn: conn, channel: channel} do
|
||||
conn = put(conn, Routes.channel_path(conn, :update, channel), channel: @update_attrs)
|
||||
assert redirected_to(conn) == Routes.channel_path(conn, :show, channel)
|
||||
|
||||
conn = get(conn, Routes.channel_path(conn, :show, channel))
|
||||
assert html_response(conn, 200)
|
||||
end
|
||||
|
||||
test "renders errors when data is invalid", %{conn: conn, channel: channel} do
|
||||
conn = put(conn, Routes.channel_path(conn, :update, channel), channel: @invalid_attrs)
|
||||
assert html_response(conn, 200) =~ "Edit Channel"
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete channel" do
|
||||
setup [:create_channel]
|
||||
|
||||
test "deletes chosen channel", %{conn: conn, channel: channel} do
|
||||
conn = delete(conn, Routes.channel_path(conn, :delete, channel))
|
||||
assert redirected_to(conn) == Routes.channel_path(conn, :index)
|
||||
assert_error_sent 404, fn ->
|
||||
get(conn, Routes.channel_path(conn, :show, channel))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp create_channel(_) do
|
||||
channel = fixture(:channel)
|
||||
{:ok, channel: channel}
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue