summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimponic <loganhunt@simponic.xyz>2023-02-01 22:55:06 -0700
committerGitHub <noreply@github.com>2023-02-01 22:55:06 -0700
commit47d3f28f16bdd1eb7ad99b91f7963fd2c4b8f9bf (patch)
tree2682c4ee63fcf67db688bf61077419f521932012
parenta26256700d93392dfc92bfc6dc6842dac842cc15 (diff)
downloadchessh-47d3f28f16bdd1eb7ad99b91f7963fd2c4b8f9bf.tar.gz
chessh-47d3f28f16bdd1eb7ad99b91f7963fd2c4b8f9bf.zip
Discord threads (#16)
* Add initial support for discord threads * Finish thread creation
-rw-r--r--.env.example7
-rw-r--r--config/config.exs6
-rw-r--r--config/runtime.exs5
-rw-r--r--lib/chessh/discord/notifier.ex140
-rw-r--r--lib/chessh/schema/game.ex5
-rw-r--r--lib/chessh/ssh/client/game/game.ex19
-rw-r--r--priv/repo/migrations/20230202004927_add_discord_thread_id.exs9
7 files changed, 153 insertions, 38 deletions
diff --git a/.env.example b/.env.example
index 51168fc..6636fb9 100644
--- a/.env.example
+++ b/.env.example
@@ -17,6 +17,7 @@ SERVER_REDIRECT_URI=http://127.0.0.1:3000/api/oauth/redirect
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_USER_AGENT=
+DISCORD_BOT_TOKEN=
JWT_SECRET=aVerySecretJwtSigningSecret
@@ -27,6 +28,6 @@ REACT_APP_SSH_PORT=42069
REDIS_HOST=localhost
REDIS_PORT=6379
-NEW_GAME_PINGABLE_ROLE_ID=1123232
-NEW_GAME_CHANNEL_WEBHOOK=https://discordapp.com/api/webhooks/
-REMIND_MOVE_CHANNEL_WEBHOOK=https://discordapp.com/api/webhooks/ \ No newline at end of file
+NEW_GAME_PINGABLE_ROLE_ID=10
+REMIND_MOVE_CHANNEL_ID=91
+NEW_GAME_CHANNEL_ID=91
diff --git a/config/config.exs b/config/config.exs
index e732049..5ada6e6 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -15,7 +15,7 @@ config :chessh, RateLimits,
player_public_keys: 15,
create_game_ms: 60 * 1000,
create_game_rate: 3,
- discord_notification_rate: 3,
+ discord_notification_rate: 30,
discord_notification_rate_ms: 1000
config :chessh, Web,
@@ -24,8 +24,8 @@ config :chessh, Web,
discord_scope: "identify"
config :chessh, DiscordNotifications,
- game_move_notif_delay_ms: 3 * 60 * 1000,
- game_created_notif_delay_ms: 30 * 1000,
+ game_move_notif_delay_ms: 10 * 1000,
+ game_created_notif_delay_ms: 10 * 1000,
reschedule_delay: 5 * 1000
config :joken, default_signer: "secret"
diff --git a/config/runtime.exs b/config/runtime.exs
index 684e48e..5da6d47 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -5,8 +5,9 @@ config :chessh,
config :chessh, DiscordNotifications,
looking_for_games_role_mention: "<@&#{System.get_env("NEW_GAME_PINGABLE_ROLE_ID")}>",
- discord_game_move_notif_webhook: System.get_env("REMIND_MOVE_CHANNEL_WEBHOOK"),
- discord_new_game_notif_webhook: System.get_env("NEW_GAME_CHANNEL_WEBHOOK")
+ remind_move_channel_id: System.get_env("REMIND_MOVE_CHANNEL_ID"),
+ discord_bot_token: System.get_env("DISCORD_BOT_TOKEN"),
+ new_game_channel_id: System.get_env("NEW_GAME_CHANNEL_ID")
config :chessh, Web,
discord_client_id: System.get_env("DISCORD_CLIENT_ID"),
diff --git a/lib/chessh/discord/notifier.ex b/lib/chessh/discord/notifier.ex
index 6d7bd46..02b7c5f 100644
--- a/lib/chessh/discord/notifier.ex
+++ b/lib/chessh/discord/notifier.ex
@@ -28,7 +28,7 @@ defmodule Chessh.DiscordNotifier do
case Hammer.check_rate_inc(
:redis,
- "discord-webhook-message-rate",
+ "discord-rate",
discord_notification_rate_ms,
discord_notification_rate,
1
@@ -50,9 +50,9 @@ defmodule Chessh.DiscordNotifier do
end
defp send_notification({:move_reminder, game_id}) do
- [min_delta_t, discord_game_move_notif_webhook] =
+ [min_delta_t, remind_move_channel_id] =
Application.get_env(:chessh, DiscordNotifications)
- |> Keyword.take([:game_move_notif_delay_ms, :discord_game_move_notif_webhook])
+ |> Keyword.take([:game_move_notif_delay_ms, :remind_move_channel_id])
|> Keyword.values()
case Repo.get(Game, game_id) |> Repo.preload([:dark_player, :light_player]) do
@@ -62,13 +62,27 @@ defmodule Chessh.DiscordNotifier do
turn: turn,
updated_at: last_updated,
moves: move_count,
- status: :continue
- } ->
+ status: :continue,
+ discord_thread_id: discord_thread_id
+ } = game ->
delta_t = NaiveDateTime.diff(NaiveDateTime.utc_now(), last_updated, :millisecond)
+ game =
+ if is_nil(discord_thread_id) do
+ {:ok, game} =
+ Game.changeset(game, %{
+ discord_thread_id: make_private_discord_thread_id(remind_move_channel_id, game)
+ })
+ |> Repo.update()
+
+ game
+ else
+ game
+ end
+
if delta_t >= min_delta_t do
post_discord(
- discord_game_move_notif_webhook,
+ game.discord_thread_id,
"<@#{if turn == :light, do: light_player_discord_id, else: dark_player_discord_id}> it is your move in Game #{game_id} (move #{move_count})."
)
end
@@ -78,16 +92,33 @@ defmodule Chessh.DiscordNotifier do
end
end
+ defp send_notification({:cleanup_thread, game_id}) do
+ case Repo.get(Game, game_id) |> Repo.preload([:dark_player, :light_player]) do
+ %Game{
+ discord_thread_id: discord_thread_id,
+ status: status
+ } = game ->
+ if !is_nil(discord_thread_id) && status != :continue do
+ destroy_channel(discord_thread_id)
+
+ Game.changeset(game, %{
+ discord_thread_id: nil
+ })
+ |> Repo.update()
+ end
+
+ _ ->
+ nil
+ end
+ end
+
defp send_notification({:game_created, game_id}) do
- [pingable_mention, discord_game_created_notif_webhook] =
+ [pingable_mention, new_game_channel_id] =
Application.get_env(:chessh, DiscordNotifications)
- |> Keyword.take([:looking_for_games_role_mention, :discord_new_game_notif_webhook])
+ |> Keyword.take([:looking_for_games_role_mention, :new_game_channel_id])
|> Keyword.values()
case Repo.get(Game, game_id) do
- nil ->
- nil
-
game ->
%Game{
dark_player: dark_player,
@@ -107,22 +138,85 @@ defmodule Chessh.DiscordNotifier do
end
if message do
- post_discord(discord_game_created_notif_webhook, message)
+ post_discord(new_game_channel_id, message)
end
+
+ nil ->
+ nil
+ end
+ end
+
+ defp make_private_discord_thread_id(channel_id, %Game{
+ id: game_id,
+ dark_player: %Player{discord_id: dark_player_discord_id, username: dark_username},
+ light_player: %Player{discord_id: light_player_discord_id, username: light_username}
+ }) do
+ case make_discord_api_call(
+ :post,
+ "channels/#{channel_id}/threads",
+ %{
+ # Private thread
+ type: 12,
+ name: "Game #{game_id} - #{light_username} V #{dark_username}"
+ }
+ ) do
+ {:ok, {_, _, body}} ->
+ %{"id" => thread_id} = Jason.decode!(body)
+
+ [light_player_discord_id, dark_player_discord_id]
+ |> Enum.map(fn id ->
+ make_discord_api_call(:put, 'channels/#{thread_id}/thread-members/#{id}')
+ end)
+
+ thread_id
+
+ _ ->
+ nil
end
end
- defp post_discord(webhook, message) do
- :httpc.request(
- :post,
- {
- String.to_charlist(webhook),
+ defp post_discord(channel_id, message) do
+ make_discord_api_call(:post, "channels/#{channel_id}/messages", %{content: message})
+ end
+
+ defp destroy_channel(channel_id) do
+ make_discord_api_call(:delete, "channels/#{channel_id}")
+ end
+
+ defp make_discord_api_call(method, route),
+ do:
+ :httpc.request(
+ method,
+ {
+ 'https://discord.com/api/#{route}',
+ [
+ make_authorization_header()
+ ]
+ },
[],
- 'application/json',
- %{content: message} |> Jason.encode!() |> String.to_charlist()
- },
- [],
- []
- )
+ []
+ )
+
+ defp make_discord_api_call(method, route, body),
+ do:
+ :httpc.request(
+ method,
+ {
+ 'https://discord.com/api/#{route}',
+ [
+ make_authorization_header()
+ ],
+ 'application/json',
+ body
+ |> Jason.encode!()
+ |> String.to_charlist()
+ },
+ [],
+ []
+ )
+
+ defp make_authorization_header() do
+ bot_token = Application.get_env(:chessh, DiscordNotifications)[:discord_bot_token]
+ {'Authorization', 'Bot #{bot_token}'}
end
end
diff --git a/lib/chessh/schema/game.ex b/lib/chessh/schema/game.ex
index b7893f1..55b9ea4 100644
--- a/lib/chessh/schema/game.ex
+++ b/lib/chessh/schema/game.ex
@@ -15,6 +15,8 @@ defmodule Chessh.Game do
belongs_to(:light_player, Player, foreign_key: :light_player_id)
belongs_to(:dark_player, Player, foreign_key: :dark_player_id)
+ field(:discord_thread_id, :string)
+
timestamps()
end
@@ -28,7 +30,8 @@ defmodule Chessh.Game do
:status,
:last_move,
:light_player_id,
- :dark_player_id
+ :dark_player_id,
+ :discord_thread_id
])
end
end
diff --git a/lib/chessh/ssh/client/game/game.ex b/lib/chessh/ssh/client/game/game.ex
index d6b0b5b..6b2ef60 100644
--- a/lib/chessh/ssh/client/game/game.ex
+++ b/lib/chessh/ssh/client/game/game.ex
@@ -396,7 +396,7 @@ defmodule Chessh.SSH.Client.Game do
{:ok, status} ->
{:ok, fen} = :binbo.get_fen(binbo_pid)
- {:ok, _new_game} =
+ {:ok, %Game{status: after_move_status}} =
game
|> Game.changeset(
Map.merge(
@@ -413,11 +413,18 @@ defmodule Chessh.SSH.Client.Game do
:syn.publish(:games, {:game, game_id}, {:new_move, attempted_move})
- GenServer.cast(
- :discord_notifier,
- {:schedule_notification, {:move_reminder, game_id},
- Application.get_env(:chessh, DiscordNotifications)[:game_move_notif_delay_ms]}
- )
+ if after_move_status == :continue do
+ GenServer.cast(
+ :discord_notifier,
+ {:schedule_notification, {:move_reminder, game_id},
+ Application.get_env(:chessh, DiscordNotifications)[:game_move_notif_delay_ms]}
+ )
+ else
+ GenServer.cast(
+ :discord_notifier,
+ {:schedule_notification, {:cleanup_thread, game_id}, 0}
+ )
+ end
_ ->
nil
diff --git a/priv/repo/migrations/20230202004927_add_discord_thread_id.exs b/priv/repo/migrations/20230202004927_add_discord_thread_id.exs
new file mode 100644
index 0000000..8c7c83a
--- /dev/null
+++ b/priv/repo/migrations/20230202004927_add_discord_thread_id.exs
@@ -0,0 +1,9 @@
+defmodule Chessh.Repo.Migrations.AddDiscordThreadId do
+ use Ecto.Migration
+
+ def change do
+ alter table(:games) do
+ add(:discord_thread_id, :string, null: true)
+ end
+ end
+end