From f6fbffab85acf256c49caed991314058c931cb8f Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Mon, 9 Oct 2023 10:43:20 -0600 Subject: [PATCH] add colors to chatssh --- config/config.exs | 2 +- config/prod.exs | 2 +- lib/chessh/ssh/client/client.ex | 4 +- lib/chessh/ssh/client/game/game.ex | 2 - lib/chessh/ssh/client/game/previous_game.ex | 2 - lib/chessh/ssh/client/game/promotion.ex | 4 +- lib/chessh/ssh/client/menus/main_menu.ex | 2 +- .../client/menus/select_paginate_poller.ex | 8 +- lib/chessh/ssh/client/screen.ex | 6 +- lib/chessh/ssh/client/trongle_chat.ex | 102 ++++++++++++------ 10 files changed, 82 insertions(+), 52 deletions(-) diff --git a/config/config.exs b/config/config.exs index 33f88cb..097d3cd 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,7 +12,7 @@ config :chessh, RateLimits, jail_attempt_threshold: 15, max_concurrent_user_sessions: 5, player_session_message_burst_ms: 500, - player_session_message_burst_rate: 8, + player_session_message_burst_rate: 10, player_public_keys: 15, create_game_ms: 60 * 1000, create_game_rate: 3, diff --git a/config/prod.exs b/config/prod.exs index 71275ea..4f80997 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -8,5 +8,5 @@ config :chessh, RateLimits, jail_timeout_ms: 5 * 60 * 1000, jail_attempt_threshold: 15, max_concurrent_user_sessions: 5, - player_session_message_burst_ms: 750, + player_session_message_burst_ms: 500, player_session_message_burst_rate: 8 diff --git a/lib/chessh/ssh/client/client.ex b/lib/chessh/ssh/client/client.ex index 1d33dd2..ffb4884 100644 --- a/lib/chessh/ssh/client/client.ex +++ b/lib/chessh/ssh/client/client.ex @@ -94,8 +94,6 @@ defmodule Chessh.SSH.Client do def handle( {:data, data}, %State{ - width: width, - height: height, screen_pid: screen_pid, player_session: player_session } = state @@ -111,7 +109,7 @@ defmodule Chessh.SSH.Client do {:noreply, state} action -> - send(screen_pid, {:input, width, height, action, data}) + send(screen_pid, {:input, action, data}) {:noreply, state} end end diff --git a/lib/chessh/ssh/client/game/game.ex b/lib/chessh/ssh/client/game/game.ex index 6103439..e996632 100644 --- a/lib/chessh/ssh/client/game/game.ex +++ b/lib/chessh/ssh/client/game/game.ex @@ -218,8 +218,6 @@ defmodule Chessh.SSH.Client.Game do end def input( - _width, - _height, action, _data, %State{ diff --git a/lib/chessh/ssh/client/game/previous_game.ex b/lib/chessh/ssh/client/game/previous_game.ex index 3582cc9..6f80f6f 100644 --- a/lib/chessh/ssh/client/game/previous_game.ex +++ b/lib/chessh/ssh/client/game/previous_game.ex @@ -54,8 +54,6 @@ defmodule Chessh.SSH.Client.PreviousGame do end def input( - _width, - _height, action, _data, %State{ diff --git a/lib/chessh/ssh/client/game/promotion.ex b/lib/chessh/ssh/client/game/promotion.ex index 693ec3f..d9e9971 100644 --- a/lib/chessh/ssh/client/game/promotion.ex +++ b/lib/chessh/ssh/client/game/promotion.ex @@ -45,9 +45,7 @@ defmodule Chessh.SSH.Client.Game.PromotionScreen do end def input( - _, - _, - _, + _action, data, %State{client_pid: client_pid, game_pid: game_pid, game_state: %Game.State{} = game_state} = state diff --git a/lib/chessh/ssh/client/menus/main_menu.ex b/lib/chessh/ssh/client/menus/main_menu.ex index 43f916e..e858893 100644 --- a/lib/chessh/ssh/client/menus/main_menu.ex +++ b/lib/chessh/ssh/client/menus/main_menu.ex @@ -35,7 +35,7 @@ defmodule Chessh.SSH.Client.MainMenu do {"Previous Games", {Chessh.SSH.Client.SelectPreviousGame, %Chessh.SSH.Client.SelectPaginatePoller.State{player_session: player_session}}}, - {"TrongleChat", + {"ChatsSH", {Chessh.SSH.Client.TrongleChat, %Chessh.SSH.Client.TrongleChat.State{player_session: player_session}}} ] diff --git a/lib/chessh/ssh/client/menus/select_paginate_poller.ex b/lib/chessh/ssh/client/menus/select_paginate_poller.ex index 5c668a0..e46fd9f 100644 --- a/lib/chessh/ssh/client/menus/select_paginate_poller.ex +++ b/lib/chessh/ssh/client/menus/select_paginate_poller.ex @@ -27,6 +27,8 @@ defmodule Chessh.SSH.Client.SelectPaginatePoller do options: [], tick: 0, cursor: nil, + width: 0, + height: 0, extra_info: %{} end @@ -112,15 +114,15 @@ defmodule Chessh.SSH.Client.SelectPaginatePoller do {:send_to_ssh, render_state(width, height, state)} ) - state + %State{state | width: width, height: height} end def input( - width, - height, action, _data, %State{ + width: width, + height: height, client_pid: client_pid, options: options, selected_option_idx: selected_option_idx diff --git a/lib/chessh/ssh/client/screen.ex b/lib/chessh/ssh/client/screen.ex index 20273a5..88beb18 100644 --- a/lib/chessh/ssh/client/screen.ex +++ b/lib/chessh/ssh/client/screen.ex @@ -1,8 +1,6 @@ defmodule Chessh.SSH.Client.Screen do @callback render(width :: integer(), height :: integer(), state :: any()) :: any() @callback input( - width :: integer(), - height :: integer(), action :: any(), data :: String.t(), state :: any() @@ -17,8 +15,8 @@ defmodule Chessh.SSH.Client.Screen do def handle_info({:render, width, height}, state), do: {:noreply, render(width, height, state)} - def handle_info({:input, width, height, action, data}, state), - do: {:noreply, input(width, height, action, data, state)} + def handle_info({:input, action, data}, state), + do: {:noreply, input(action, data, state)} end end end diff --git a/lib/chessh/ssh/client/trongle_chat.ex b/lib/chessh/ssh/client/trongle_chat.ex index 10df66d..4964965 100644 --- a/lib/chessh/ssh/client/trongle_chat.ex +++ b/lib/chessh/ssh/client/trongle_chat.ex @@ -3,6 +3,16 @@ defmodule Chessh.SSH.Client.TrongleChat do alias Chessh.{Player, Chat, Utils, Repo, PlayerSession} import Ecto.Query + @colors [ + IO.ANSI.light_blue(), + IO.ANSI.light_red(), + IO.ANSI.green(), + IO.ANSI.light_magenta(), + IO.ANSI.cyan(), + IO.ANSI.blue(), + IO.ANSI.yellow() + ] + defmodule State do defstruct client_pid: nil, message: "", @@ -26,36 +36,28 @@ defmodule Chessh.SSH.Client.TrongleChat do {:noreply, render(width, height, new_state)} end - defp get_initial_chats() do - from(c in Chat, - order_by: [desc: c.id], - limit: 100 - ) - |> Repo.all() - |> Repo.preload([:chatter]) - end - - defp get_player(%PlayerSession{player_id: player_id}), do: Repo.get!(Player, player_id) - - def init([%State{client_pid: client_pid, player_session: player_session} = state]) do + def init([ + %State{ + client_pid: client_pid, + player_session: %PlayerSession{player_id: player_id} = player_session + } = state + ]) do :syn.add_node_to_scopes([:chat]) :ok = :syn.join(:chat, {:tronglechat}, self()) send(client_pid, {:send_to_ssh, Utils.clear_codes()}) - chats = get_initial_chats() - {:ok, %State{ state - | chats: chats, - player_session: %PlayerSession{player_session | player: get_player(player_session)} + | chats: get_initial_chats(), + player_session: %PlayerSession{player_session | player: Repo.get!(Player, player_id)} }} end def render( - _width, - _height, + width, + height, %State{ client_pid: client_pid, chats: chats, @@ -64,38 +66,39 @@ defmodule Chessh.SSH.Client.TrongleChat do } = state ) do chat_msgs = - Enum.map(chats, fn %Chat{message: message, chatter: %Player{username: chat_username}} = - _chat -> - chat_username <> "> " <> message - end) + chats + |> Enum.slice(0, height - 1) + |> Enum.map(&format_chat/1) |> Enum.join("\r\n") - prompt = username <> "> " <> message + {prompt, prompt_len} = format_prompt(username, message) send( client_pid, {:send_to_ssh, [ Utils.clear_codes(), - prompt <> "\r\n" <> chat_msgs <> IO.ANSI.cursor(0, String.length(prompt)) + prompt <> + "\r\n" <> chat_msgs <> IO.ANSI.cursor(0, prompt_len + 1) ]} ) - state + %State{state | width: width, height: height} end def input( - width, - height, action, data, %State{ player_session: %PlayerSession{player: player}, - chats: chats, - message: message + message: message, + width: width, + height: height } = state ) do - appended_message = + safe_char_regex = ~r/[A-Za-z0-9._~()'!*:@,;+?-]/ + + appended_message_state = case action do :backspace -> %State{state | message: String.slice(message, 0..-2)} @@ -110,13 +113,48 @@ defmodule Chessh.SSH.Client.TrongleChat do end _ -> - if String.match?(data, ~r/[a-zA-Z \.!-]/) do + if String.match?(data, safe_char_regex) do %State{state | message: message <> data} else state end end - render(width, height, appended_message) + render(width, height, appended_message_state) + end + + defp get_initial_chats() do + from(c in Chat, + order_by: [desc: c.id], + limit: 100 + ) + |> Repo.all() + |> Repo.preload([:chatter]) + end + + defp username_color(username, colors \\ @colors) do + ind = + String.to_charlist(username) + |> Enum.sum() + |> rem(length(colors)) + + Enum.at(colors, ind) + end + + defp format_prompt(username, message) do + { + [ + IO.ANSI.format_fragment([username_color(username), username, IO.ANSI.default_color()]), + "> ", + message + ] + |> Enum.join(""), + String.length(username) + String.length(message) + 2 + } + end + + defp format_chat(%Chat{chatter: %Player{username: username}, message: message}) do + {prompt, _} = format_prompt(username, message) + prompt end end