summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorArjun Satarkar <me@arjunsatarkar.net>2024-07-27 15:34:28 +0000
committerArjun Satarkar <me@arjunsatarkar.net>2024-07-27 15:34:28 +0000
commit407862a4e0e10ce2a035e4e5e76e26dde849aac1 (patch)
treedb8fe1c95672d31621c6bd5fe545cab5497f18df /lib
parentcd794243a5bba358d995e26ba024268e7d5d3f85 (diff)
downloadmediasync-407862a4e0e10ce2a035e4e5e76e26dde849aac1.tar
mediasync-407862a4e0e10ce2a035e4e5e76e26dde849aac1.tar.gz
mediasync-407862a4e0e10ce2a035e4e5e76e26dde849aac1.zip
Finish Discord activity implementation
Diffstat (limited to 'lib')
-rw-r--r--lib/mediasync/application.ex7
-rw-r--r--lib/mediasync/discord_api.ex15
-rw-r--r--lib/mediasync/http_errors.ex34
-rw-r--r--lib/mediasync/room.ex23
-rw-r--r--lib/mediasync/router.ex116
-rw-r--r--lib/mediasync/utils.ex6
6 files changed, 163 insertions, 38 deletions
diff --git a/lib/mediasync/application.ex b/lib/mediasync/application.ex
index 1e8f6d4..5cef37a 100644
--- a/lib/mediasync/application.ex
+++ b/lib/mediasync/application.ex
@@ -25,6 +25,13 @@ defmodule Mediasync.Application do
{Registry, keys: :duplicate, name: Mediasync.RoomSubscriptionRegistry}
]
+ children =
+ if Application.fetch_env!(:mediasync, :enable_discord_activity?) do
+ [{Registry, keys: :duplicate, name: Mediasync.DiscordActivityInstanceRegistry} | children]
+ else
+ children
+ end
+
System.no_halt(true)
# See https://hexdocs.pm/elixir/Supervisor.html
diff --git a/lib/mediasync/discord_api.ex b/lib/mediasync/discord_api.ex
new file mode 100644
index 0000000..da1d39c
--- /dev/null
+++ b/lib/mediasync/discord_api.ex
@@ -0,0 +1,15 @@
+defmodule Mediasync.DiscordAPI do
+ @base_url "https://discord.com/api/v10/"
+
+ @spec get_user!(String.t()) :: map()
+ def get_user!(access_token) do
+ response =
+ Req.get!(@base_url <> "users/@me", headers: [authorization: "Bearer #{access_token}"])
+
+ if response.status == 200 do
+ response.body
+ else
+ raise "Discord API responded with status code #{response.status}"
+ end
+ end
+end
diff --git a/lib/mediasync/http_errors.ex b/lib/mediasync/http_errors.ex
index c38486a..cbbb613 100644
--- a/lib/mediasync/http_errors.ex
+++ b/lib/mediasync/http_errors.ex
@@ -4,8 +4,8 @@ defmodule Mediasync.HTTPErrors do
@video_url_too_large Jason.encode!(
%{
- "error" => "video_url_too_large",
- "max_size" => Application.compile_env(:mediasync, :max_video_url_size)
+ "error" => "videoUrlTooLarge",
+ "maxSize" => Application.compile_env(:mediasync, :max_video_url_size)
},
pretty: true
)
@@ -21,7 +21,7 @@ defmodule Mediasync.HTTPErrors do
)
end
- @invalid_video_url Jason.encode!(%{"error" => "invalid_video_url"}, pretty: true)
+ @invalid_video_url Jason.encode!(%{"error" => "invalidVideoUrl"}, pretty: true)
@spec send_invalid_video_url(Plug.Conn.t()) :: Plug.Conn.t()
@spec send_invalid_video_url(Plug.Conn.t(), []) :: Plug.Conn.t()
@@ -36,7 +36,7 @@ defmodule Mediasync.HTTPErrors do
@not_found Jason.encode!(
%{
- "error" => "not_found",
+ "error" => "notFound",
"message" => "No page was found at this location."
},
pretty: true
@@ -68,7 +68,7 @@ defmodule Mediasync.HTTPErrors do
@invalid_csrf_token Jason.encode!(
%{
- "error" => "invalid_csrf_token",
+ "error" => "invalidCsrfToken",
"message" => "Try reloading the previous page and retrying."
},
pretty: true
@@ -82,7 +82,29 @@ defmodule Mediasync.HTTPErrors do
|> send_resp(400, @invalid_csrf_token)
end
- @bad_gateway Jason.encode!(%{"error" => "bad_gateway"}, pretty: true)
+ @spec send_bad_request(Plug.Conn.t()) :: Plug.Conn.t()
+ @spec send_bad_request(Plug.Conn.t(), message: String.t() | nil) :: Plug.Conn.t()
+ def send_bad_request(conn), do: send_bad_request(conn, message: nil)
+ def send_bad_request(conn, []), do: send_bad_request(conn, message: nil)
+
+ def send_bad_request(conn, message: message) do
+ error = %{
+ "error" => "badRequest"
+ }
+
+ error =
+ if message do
+ Map.put(error, "message", message)
+ else
+ error
+ end
+
+ conn
+ |> put_json_content_type()
+ |> send_resp(400, Jason.encode!(error))
+ end
+
+ @bad_gateway Jason.encode!(%{"error" => "badGateway"}, pretty: true)
@spec send_bad_gateway(Plug.Conn.t()) :: Plug.Conn.t()
@spec send_bad_gateway(Plug.Conn.t(), []) :: Plug.Conn.t()
diff --git a/lib/mediasync/room.ex b/lib/mediasync/room.ex
index 0367f40..6386893 100644
--- a/lib/mediasync/room.ex
+++ b/lib/mediasync/room.ex
@@ -18,6 +18,9 @@ defmodule Mediasync.Room.State do
:video_info,
:host_user_token_hash,
:room_id,
+ :host_username,
+ :viewer_usernames,
+ :discord_instance_id,
host_disconnected_tries: @host_disconnected_tries_max
]
@@ -28,7 +31,10 @@ defmodule Mediasync.Room.State do
@type t() :: %Mediasync.Room.State{
video_info: Mediasync.Room.VideoInfo.t(),
host_user_token_hash: Mediasync.UserToken.hash(),
- room_id: Mediasync.RoomID.t(),
+ room_id: Mediasync.RoomID.t() | nil,
+ host_username: String.t() | nil,
+ viewer_usernames: [String.t()] | nil,
+ discord_instance_id: String.t() | nil,
host_disconnected_tries: integer()
}
end
@@ -90,10 +96,19 @@ defmodule Mediasync.Room do
@impl true
@spec init(Mediasync.Room.State.t()) :: {:ok, Mediasync.Room.State.t()}
- def init(room_state = %Mediasync.Room.State{}) do
+ def init(state = %Mediasync.Room.State{}) do
+ if state.discord_instance_id do
+ Registry.register(Mediasync.DiscordActivityInstanceRegistry, state.discord_instance_id, %{
+ host_username: state.host_username,
+ room_id: state.room_id
+ })
+ end
+
Process.send_after(self(), :check_if_active, @inactive_check_wait_milliseconds)
- {:ok, room_state}
+ Logger.info("Created room #{state.room_id}")
+
+ {:ok, state}
end
@impl true
@@ -141,7 +156,7 @@ defmodule Mediasync.Room do
Process.send_after(self(), :check_if_active, @inactive_check_wait_milliseconds)
if state.host_disconnected_tries <= 0 do
- Logger.info("Room #{state.room_id} shutting down: no host.")
+ Logger.info("Room #{state.room_id} shutting down: no host")
{:stop, {:shutdown, :no_host}, state}
else
{:noreply, state}
diff --git a/lib/mediasync/router.ex b/lib/mediasync/router.ex
index a27bfc9..c60ca8a 100644
--- a/lib/mediasync/router.ex
+++ b/lib/mediasync/router.ex
@@ -5,7 +5,7 @@ defmodule Mediasync.Router do
use Plug.Router
use Plug.ErrorHandler
- plug(Plug.Logger)
+ plug(Plug.Logger, log: :debug)
plug(Plug.Head)
plug(Plug.Static, at: "/static", from: {:mediasync, "priv/static"})
@@ -34,21 +34,19 @@ defmodule Mediasync.Router do
conn
|> put_html_content_type()
- send_resp(
- conn,
- 200,
- cond do
- enable_discord_activity? and Map.get(conn.query_params, query_param_instance_id()) ->
- Mediasync.Templates.discord_activity()
+ cond do
+ enable_discord_activity? and Map.has_key?(conn.query_params, query_param_instance_id()) ->
+ conn
+ |> put_session("discord_instance_id", conn.query_params["instance_id"])
+ |> send_resp(200, Mediasync.Templates.discord_activity())
- enable_discord_activity? and
- Map.get(conn.query_params, query_param_discord_activity_inner()) ->
- Mediasync.Templates.home(:discord_activity)
+ enable_discord_activity? and
+ Map.has_key?(conn.query_params, query_param_discord_activity_inner()) ->
+ send_resp(conn, 200, Mediasync.Templates.home(:discord_activity))
- true ->
- Mediasync.Templates.home()
- end
- )
+ true ->
+ send_resp(conn, 200, Mediasync.Templates.home())
+ end
end
post "/host_room" do
@@ -73,24 +71,32 @@ defmodule Mediasync.Router do
Mediasync.HTTPErrors.send_invalid_video_url(conn)
true ->
+ in_discord_activity? =
+ Application.fetch_env!(:mediasync, :enable_discord_activity?) and
+ Map.has_key?(conn.query_params, query_param_discord_activity_inner())
+
+ {suffix, host_username, instance_id} =
+ if in_discord_activity? do
+ {"?#{query_param_discord_activity_inner()}",
+ Mediasync.DiscordAPI.get_user!(get_session(conn, "discord_access_token"))[
+ "username"
+ ], get_session(conn, "discord_instance_id")}
+ else
+ {"", nil, nil}
+ end
+
{:ok, _pid, room_id} =
DynamicSupervisor.start_child(
Mediasync.RoomSupervisor,
{Mediasync.Room,
%Mediasync.Room.State{
video_info: video_info,
- host_user_token_hash: get_user_token_hash!(conn)
+ host_user_token_hash: get_user_token_hash!(conn),
+ host_username: host_username,
+ discord_instance_id: instance_id
}}
)
- suffix =
- if Application.fetch_env!(:mediasync, :enable_discord_activity?) and
- Map.get(conn.query_params, query_param_discord_activity_inner()) do
- "?#{query_param_discord_activity_inner()}"
- else
- ""
- end
-
redirect(conn, status: 303, location: "/room/#{room_id}#{suffix}")
end
end
@@ -104,7 +110,7 @@ defmodule Mediasync.Router do
{video_info, websocket_path, state_url, home_url} =
if Application.fetch_env!(:mediasync, :enable_discord_activity?) and
- Map.get(conn.query_params, query_param_discord_activity_inner()) do
+ Map.has_key?(conn.query_params, query_param_discord_activity_inner()) do
{%{video_info | url: "/.proxy/room/#{room_id}/video"},
"/.proxy/room/#{room_id}/websocket?#{query_param_discord_activity_inner()}",
"/.proxy/room/#{room_id}/state.json?#{query_param_discord_activity_inner()}",
@@ -187,6 +193,60 @@ defmodule Mediasync.Router do
end
end
+ post "/discord_activity/access_token" do
+ if Application.fetch_env!(:mediasync, :enable_discord_activity?) do
+ if Map.has_key?(conn.query_params, query_param_discord_activity_inner()) do
+ response =
+ Req.post!("https://discord.com/api/oauth2/token",
+ headers: [
+ content_type: "application/x-www-form-urlencoded"
+ ],
+ form: [
+ client_id: Application.fetch_env!(:mediasync, :discord_client_id),
+ client_secret: Application.fetch_env!(:mediasync, :discord_client_secret),
+ grant_type: "authorization_code",
+ code: conn.body_params["code"]
+ ]
+ )
+
+ access_token = response.body["access_token"]
+
+ conn
+ |> put_session("discord_access_token", access_token)
+ |> put_plain_text_content_type()
+ |> send_resp(200, access_token)
+ else
+ # If the query param isn't present, we won't be able to modify the session (see session_wrapper/2).
+ Mediasync.HTTPErrors.send_bad_request(conn,
+ message:
+ "This route must always be called with the query param ?#{query_param_discord_activity_inner()}"
+ )
+ end
+ else
+ Mediasync.HTTPErrors.send_not_found(conn)
+ end
+ end
+
+ get "/discord_activity/rooms_for_instance" do
+ if Application.fetch_env!(:mediasync, :enable_discord_activity?) do
+ values =
+ for {_pid, value} <-
+ Registry.match(
+ Mediasync.DiscordActivityInstanceRegistry,
+ conn.query_params["instance_id"],
+ :_
+ ) do
+ value
+ end
+
+ conn
+ |> put_json_content_type()
+ |> send_resp(200, Jason.encode!(values))
+ else
+ Mediasync.HTTPErrors.send_not_found(conn)
+ end
+ end
+
match _ do
Mediasync.HTTPErrors.send_not_found(conn)
end
@@ -206,10 +266,10 @@ defmodule Mediasync.Router do
query_params = fetch_query_params(conn).query_params
in_discord_activity? =
- !!(Application.fetch_env!(:mediasync, :enable_discord_activity?) &&
- (Map.get(query_params, query_param_discord_activity_inner()) ||
- (conn.request_path == "/" &&
- Map.get(query_params, query_param_instance_id()))))
+ Application.fetch_env!(:mediasync, :enable_discord_activity?) and
+ (Map.has_key?(query_params, query_param_discord_activity_inner()) or
+ (conn.request_path == "/" and
+ Map.has_key?(query_params, query_param_instance_id())))
Plug.Session.call(
conn,
diff --git a/lib/mediasync/utils.ex b/lib/mediasync/utils.ex
index 407a9a4..c2f3531 100644
--- a/lib/mediasync/utils.ex
+++ b/lib/mediasync/utils.ex
@@ -13,6 +13,12 @@ defmodule Mediasync.Utils do
put_resp_content_type(conn, "application/json")
end
+ @spec put_plain_text_content_type(Plug.Conn.t()) :: Plug.Conn.t()
+ @spec put_plain_text_content_type(Plug.Conn.t(), []) :: Plug.Conn.t()
+ def put_plain_text_content_type(conn, _opts \\ []) do
+ put_resp_content_type(conn, "text/plain")
+ end
+
@spec redirect(Plug.Conn.t(), status: Plug.Conn.status(), location: binary()) :: Plug.Conn.t()
def redirect(conn, status: status, location: location) do
conn