1
0
mirror of https://github.com/algora-io/tv.git synced 2025-03-17 20:17:45 +02:00
2024-08-15 17:59:24 +03:00

111 lines
3.0 KiB
Elixir

defmodule Algora.Youtube.Chat do
@youtube_headers [
{"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"},
{"Accept-Language", "en-US"}
]
def get_video_data(urls) do
urls
|> Enum.reduce(nil, fn url, acc ->
case fetch_response(url) do
{:ok, response} -> response
_ -> acc
end
end)
|> handle_response()
end
def fetch_response(url) do
HTTPoison.get(url, @youtube_headers)
end
defp handle_response(nil), do: {:error, {"Stream not found", 404}}
defp handle_response(%HTTPoison.Response{status_code: 404}),
do: {:error, {"Stream not found", 404}}
defp handle_response(%HTTPoison.Response{status_code: status}) when status != 200,
do: {:error, {"Failed to fetch stream: #{status}", status}}
defp handle_response(%HTTPoison.Response{body: body}) do
case Regex.run(
~r/(?:window\s*\[\s*["']ytInitialData["']\s*\]|ytInitialData)\s*=\s*({.+?})\s*;/,
body
) do
[_, initial_data] ->
case Regex.run(~r/(?:ytcfg.set)\(({[\s\S]+?})\)\s*;/, body) do
[_, config_str] ->
config = Jason.decode!(config_str)
if Map.has_key?(config, "INNERTUBE_API_KEY") and
Map.has_key?(config, "INNERTUBE_CONTEXT") do
{:ok, %{initial_data: initial_data, config: Map.put(config, "hl", "US")}}
else
{:error, {"Failed to load YouTube context", 500}}
end
_ ->
{:error, {"Failed to parse config", 500}}
end
_ ->
{:error, {"Failed to parse initial data", 500}}
end
end
def get_continuation_token(continuation) when is_map(continuation) do
continuation
|> Enum.find_value(nil, fn
{_key, %{"continuation" => continuation_token}} -> continuation_token
_ -> nil
end)
end
def get_continuation_token(_continuation), do: nil
def get_id(data) when is_map(data) do
data
|> Map.delete("clickTrackingParams")
|> traverse_map()
end
defp traverse_map(map) do
case Map.to_list(map) do
[{_action_type, %{"item" => action}}] ->
case Map.to_list(action) do
[{_renderer_type, %{"id" => id}}] -> id
_ -> nil
end
_ ->
nil
end
end
def find_key_value(json_string, key, target_value) do
case Jason.decode(json_string) do
{:ok, decoded_json} ->
find_in_nested(decoded_json, key, target_value)
{:error, error} ->
IO.puts("Error decoding JSON: #{inspect(error)}")
end
end
defp find_in_nested(nil, _key, _target_value), do: nil
defp find_in_nested(map = %{}, key, target_value) do
Enum.find_value(map, fn
{^key, ^target_value} -> map
{_k, v} -> find_in_nested(v, key, target_value)
end)
end
defp find_in_nested([head | tail], key, target_value) do
find_in_nested(head, key, target_value) || find_in_nested(tail, key, target_value)
end
defp find_in_nested(_value, _key, _target_value), do: nil
end