Skip to content

Commit

Permalink
Merge pull request #8 from papey/refactoring/cmd-handlers
Browse files Browse the repository at this point in the history
Rework commands handler mechanism
  • Loading branch information
papey authored Jan 31, 2022
2 parents caa4edd + 84e84cb commit 919058a
Show file tree
Hide file tree
Showing 16 changed files with 510 additions and 575 deletions.
67 changes: 30 additions & 37 deletions lib/blindtest/blindtest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -262,42 +262,6 @@ defmodule BlindTest do
|> String.downcase()
end

@doc """
Verify if current answer is the first field, the second field or both
Returns an atom describing the answer status
## Examples
iex> BlindTest.verify_answer(%BlindTest.GuessEntry{f1s: ["Spiritbox"], f2s: ["Holly Roller"]}, "spiritbox holl roller")
:both
"""
def verify_answer(expected, proposal, threshold \\ 0.2) do
sanitized = sanitize_input(proposal)

valid? =
&(Levenshtein.distance(&1, sanitized) /
String.length(Enum.max([&1, sanitized])) < threshold)

both_combinations =
for f1 <- expected.f1s, f2 <- expected.f2s do
["#{f1} #{f2}", "#{f2} #{f1}"]
end

cond do
Enum.find_value(List.flatten(both_combinations), false, &valid?.(&1)) ->
:both

Enum.find_value(expected.f1s, false, &valid?.(&1)) ->
:f1

Enum.find_value(expected.f2s, false, &valid?.(&1)) ->
:f2

true ->
:wrong
end
end

@doc """
React and respond to validate call
Expand Down Expand Up @@ -361,6 +325,13 @@ defmodule BlindTest do
end
end

def exists?() do
case process() do
:none -> false
_ -> true
end
end

@doc """
Ensure there is no running game
Expand Down Expand Up @@ -412,7 +383,29 @@ defmodule BlindTest do
do: {:ok},
else:
{:error,
"You can only interact with blind test in channel #{Discord.channel(bt_chan_id)}"}
"To use this command, you have to interact with blind test in channel #{Discord.channel(bt_chan_id)}"}
end

def handle_message(msg, channel_id) do
msg
|> do_validate?(channel_id)
|> do_validate(msg, channel_id)
end

def do_validate?(msg, channel_id) do
BlindTest.exists?() &&
channel_id == msg.channel_id &&
BlindTest.guessing?() &&
BlindTest.plays?(msg.author.id)
end

def do_validate(false, _, _), do: :ignore

def do_validate(true, msg, channel_id) do
case Game.validate(msg.content, msg.author.id) do
{:ok, status, points} -> BlindTest.react_to_validation(msg, channel_id, status, points)
:not_guessing -> :ignore
end
end

@doc """
Expand Down
209 changes: 125 additions & 84 deletions lib/blindtest/downloader.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
defmodule Downloader do
require Logger

@moduledoc """
GenServer used to download files from a blind test song list
"""

require Logger

alias Nostrum.Api

def parse_timestamps(ts, guess_duration) do
start =
Timex.Duration.to_time!(%Timex.Duration{
microseconds: 0,
megaseconds: 0,
seconds: ts
})

to =
Timex.Duration.to_time!(%Timex.Duration{
microseconds: 0,
megaseconds: 0,
seconds: ts + trunc(guess_duration * 1.5)
})

{:ok, start, to}
end

defmodule Yydl do
@moduledoc """
Ytdl is a simple module used to try and retry youtube-dl command
Expand All @@ -14,25 +33,71 @@ defmodule Downloader do
# fake UA, use Internet Explorer 7
@user_agent "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"

@max_retry 5
@timer 1 * 1000
@max_retry 3
@timer 1 * 200

defmodule DownloadData do
defstruct [
:url,
:data_url,
:ts_from,
:ts_to,
:output,
check: false
]
end

defmodule Ffmpeg do
def args(dl_data),
do: dl_data |> base_args() |> add_check_args(dl_data) |> add_file_arg(dl_data)

defp base_args(dl_data) do
[
"-y",
"-ss",
Time.to_string(dl_data.ts_from),
"-to",
Time.to_string(dl_data.ts_to),
"-i",
dl_data.data_url,
"-c:a",
"libopus",
"-ac",
"1",
"-b:a",
"96K",
"-vbr",
"on",
"-frame_duration",
"20"
]
end

defp add_check_args(args, %DownloadData{check: check}) when check,
do: args ++ ["-f", "null"]

def get(url), do: get(url, 0)
defp add_check_args(args, _), do: args

def get(url, @max_retry),
defp add_file_arg(args, %DownloadData{output: file}), do: args ++ [file]
end

def get_url(url), do: get_url(url, 0)

def get_url(url, @max_retry),
do: {:error, "Error getting #{url}, after #{@max_retry} retries"}

def get(url, retries) do
def get_url(url, retries) do
Logger.info("Getting Youtube data URL", url: url, retries: retries)

if retries != 0 do
IO.inspect(retries)
:timer.sleep(@timer)
end

System.cmd("youtube-dl", ["--rm-cache-dir"])

case System.cmd("youtube-dl", [
"--add-header",
"Cookie:",
"--youtube-skip-dash-manifest",
"--hls-prefer-native",
"--user-agent",
@user_agent,
"-g",
Expand All @@ -41,25 +106,52 @@ defmodule Downloader do
url
]) do
{stdout, 0} ->
{:ok, stdout}
{:ok, String.trim(stdout)}

{_stderr, _code} ->
get(url, retries + 1)
get_url(url, retries + 1)
end
end

def get_data(dl_data),
do: get_data(dl_data, 0)

def get_data(%DownloadData{data_url: url}, @max_retry),
do: {:error, "unable to download url `#{url}`"}

def get_data(dl_data, retries) do
if retries != 0 do
:timer.sleep(@timer)
end

case System.cmd("ffmpeg", Ffmpeg.args(dl_data)) do
{_stdout, 0} ->
{:ok}

{_stderr, _} ->
case get_url(dl_data.url) do
{:ok, new_data_url} ->
%DownloadData{dl_data | data_url: new_data_url} |> get_data(retries + 1)

error ->
error
end
end
end
end

defmodule Worker do
@moduledoc """
Worker module downloading files
"""

use GenServer

def start_link(stack) do
IO.inspect(stack)
GenServer.start_link(__MODULE__, stack, name: __MODULE__)
end

@moduledoc """
Worker module downloading files
"""
def init({songs, cache, channel_id, _private_channel_id, _guess_duration} = stack) do
Logger.info("Starting Downloader worker", data: songs)

Expand All @@ -86,16 +178,12 @@ defmodule Downloader do
)
end

case BlindTest.process() do
{:one, _} ->
Game.set_ready()

:none ->
Api.create_message(channel_id, "Error when communicating with blind test process")
if BlindTest.exists?() do
Game.set_ready()
else
Api.create_message(channel_id, "Error when communicating with blind test process")
end

Logger.info("Downloader worker exits")

{:stop, :normal, []}
end

Expand All @@ -105,73 +193,26 @@ defmodule Downloader do
) do
Logger.info("Downloader worker, work in progress", current: current, data: rest)

with {:one, _} = BlindTest.process(),
with {:ok} = BlindTest.ensure_running(),
{:ok, uuid} <- Youtube.parse_uuid(current.url),
{:ok, ts} <- Youtube.get_timestamp(current.url),
{:ok, stdout} <- Yydl.get(current.url) do
case String.split(stdout, "\n") do
[] ->
Api.create_message(
private_channel_id,
"__Downloader status update__: no data found for url #{current.url}"
)

[data_url | _] ->
start =
Timex.Duration.to_time!(%Timex.Duration{
microseconds: 0,
megaseconds: 0,
seconds: ts
})

to =
Timex.Duration.to_time!(%Timex.Duration{
microseconds: 0,
megaseconds: 0,
seconds: ts + trunc(guess_duration * 1.5)
})

case System.cmd("ffmpeg", [
"-y",
"-ss",
Time.to_string(start),
"-to",
Time.to_string(to),
"-i",
data_url,
"-c:a",
"libopus",
"-ac",
"1",
"-b:a",
"96K",
"-vbr",
"on",
"-frame_duration",
"20",
"#{cache}/#{uuid}.opus"
]) do
{_stdout, 0} ->
{:ok, :added} = Game.add_guess(current)

{_stderr, _} ->
Api.create_message(
private_channel_id,
"__Downloader status update__: error getting raw data for url #{current.url}"
)
end
end
{:ok, start, to} <- Downloader.parse_timestamps(ts, guess_duration),
{:ok, data_url} <- Yydl.get_url(current.url),
{:ok} <-
Yydl.get_data(%Yydl.DownloadData{
data_url: data_url,
url: current.url,
ts_from: start,
ts_to: to,
output: "#{cache}/#{uuid}.opus",
check: false
}) do
Game.add_guess(current)
else
{:none} ->
Api.create_message(
private_channel_id,
"__Downloader status update__: error when communicating with blind test process"
)

{:error, reason} ->
Api.create_message(
private_channel_id,
reason
"__Downloader status update__: error when downloading #{current.url}, reason : #{reason}"
)
end

Expand Down
Loading

0 comments on commit 919058a

Please sign in to comment.