diff options
author | Arjun Satarkar <me@arjunsatarkar.net> | 2024-07-27 15:34:28 +0000 |
---|---|---|
committer | Arjun Satarkar <me@arjunsatarkar.net> | 2024-07-27 15:34:28 +0000 |
commit | 407862a4e0e10ce2a035e4e5e76e26dde849aac1 (patch) | |
tree | db8fe1c95672d31621c6bd5fe545cab5497f18df /lib | |
parent | cd794243a5bba358d995e26ba024268e7d5d3f85 (diff) | |
download | mediasync-407862a4e0e10ce2a035e4e5e76e26dde849aac1.tar mediasync-407862a4e0e10ce2a035e4e5e76e26dde849aac1.tar.gz mediasync-407862a4e0e10ce2a035e4e5e76e26dde849aac1.zip |
Finish Discord activity implementation
Diffstat (limited to 'lib')
-rw-r--r-- | lib/mediasync/application.ex | 7 | ||||
-rw-r--r-- | lib/mediasync/discord_api.ex | 15 | ||||
-rw-r--r-- | lib/mediasync/http_errors.ex | 34 | ||||
-rw-r--r-- | lib/mediasync/room.ex | 23 | ||||
-rw-r--r-- | lib/mediasync/router.ex | 116 | ||||
-rw-r--r-- | lib/mediasync/utils.ex | 6 |
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 |