Skip to content

Commit

Permalink
Merge branch 'main' into pantalaimon
Browse files Browse the repository at this point in the history
  • Loading branch information
progval committed Feb 13, 2024
2 parents 37cfc06 + 64ff97c commit ea1f134
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 100 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ jobs:
fail-fast: false
matrix:
include:
- otp: '20' # for some reason, erlef/setup-beam@v1 fails on 19
- otp: '21' # :parse_trans does not support OTP 20 anymore
elixir: '1.7.4'
os: 'ubuntu-20.04'
- otp: '22'
elixir: '1.7.4'
os: 'ubuntu-20.04'
- otp: '20'
- otp: '21'
elixir: '1.9'
os: 'ubuntu-20.04'
- otp: '22'
Expand Down
2 changes: 1 addition & 1 deletion lib/format/common.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ defmodule M51.Format do
"foo\nbar"
iex> M51.Format.matrix2irc(~s(foo <font data-mx-color="#FF0000">bar</font> baz))
"foo \x04FF0000,FFFFFFbar\x0399,99 baz"
"foo \x04FF0000bar\x0399,99 baz"
"""
def matrix2irc(html, homeserver \\ nil) do
tree = :mochiweb_html.parse("<html>" <> html <> "</html>")
Expand Down
24 changes: 15 additions & 9 deletions lib/format/matrix2irc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,14 @@ defmodule M51.Format.Matrix2Irc do
transform_children(children, state)

_ ->
fg = String.trim_leading(fg || "000000", "#")
bg = String.trim_leading(bg || "FFFFFF", "#")
fg = fg && String.trim_leading(fg, "#")
bg = bg && String.trim_leading(bg, "#")

restored_colors =
case state.color do
# reset
{nil, nil} -> "\x0399,99"
{fg, bg} -> "\x04#{fg},#{bg}"
end
restored_colors = get_color_code(state.color)

state = %M51.Format.Matrix2Irc.State{state | color: {fg, bg}}

~s(\x04#{fg},#{bg}) <>
get_color_code({fg, bg}) <>
transform_children(children, state) <> restored_colors
end
end
Expand All @@ -143,6 +138,17 @@ defmodule M51.Format.Matrix2Irc do
transform_children(children, state, char)
end

def get_color_code({fg, bg}) do
case {fg, bg} do
# reset
{nil, nil} -> "\x0399,99"
{fg, nil} -> "\x04#{fg}"
# set both fg and bg, then reset fg
{nil, bg} -> "\x04000000,#{bg}\x0399"
{fg, bg} -> "\x04#{fg},#{bg}"
end
end

defp transform_children(children, state, char \\ "") do
Stream.concat([
[char],
Expand Down
156 changes: 94 additions & 62 deletions lib/irc/handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ defmodule M51.IrcConn.Handler do
# 8kB should be a reasonable limit to remain under the allowed 65kB even
# with large signatures and many escapes.
@multiline_max_bytes 8192
def multiline_max_bytes, do: @multiline_max_bytes

# set of capabilities that we will show in CAP LS and accept with ACK;
# along with their value (shown in CAP LS 302)
Expand Down Expand Up @@ -104,6 +105,10 @@ defmodule M51.IrcConn.Handler do

@capabilities_ls Map.merge(@capabilities, @informative_capabilities)

@capability_names @capabilities
|> Enum.map(fn {name, {atom, _}} -> {atom, name} end)
|> Map.new()

@valid_batch_types ["draft/multiline"]

@doc """
Expand Down Expand Up @@ -299,6 +304,22 @@ defmodule M51.IrcConn.Handler do
end
end

defp cap_ls(is_302, send) do
caps = @capabilities_ls
|> Map.to_list()
|> Enum.sort_by(fn {k, _v} -> k end)
|> Enum.map(fn {k, {_, v}} ->
cond do
is_nil(v) -> k
!is_302 -> k
true -> k <> "=" <> v
end
end)
|> Enum.join(" ")

send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LS", caps]})
end

# Handles a connection registration command, ie. only NICK/USER/CAP/AUTHENTICATE.
# Returns nil, {:nick, new_nick}, {:user, new_gecos}, {:authenticate, user_id},
# :got_cap_ls, or :got_cap_end.
Expand Down Expand Up @@ -341,30 +362,11 @@ defmodule M51.IrcConn.Handler do
nil

{"CAP", ["LS", "302"]} ->
caps =
@capabilities_ls
|> Map.to_list()
|> Enum.sort_by(fn {k, _v} -> k end)
|> Enum.map(fn {k, {_, v}} ->
case v do
nil -> k
_ -> k <> "=" <> v
end
end)
|> Enum.join(" ")

send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LS", caps]})
cap_ls(true, send)
:got_cap_ls

{"CAP", ["LS" | _]} ->
caps =
@capabilities_ls
|> Map.to_list()
|> Enum.sort_by(fn {k, {_, _v}} -> k end)
|> Enum.map(fn {k, _v} -> k end)
|> Enum.join(" ")

send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LS", caps]})
cap_ls(false, send)
:got_cap_ls

{"CAP", ["LIST" | _]} ->
Expand Down Expand Up @@ -616,8 +618,11 @@ defmodule M51.IrcConn.Handler do
"CASEMAPPING=rfc3454",
"CLIENTTAGDENY=*,-draft/react,-draft/reply",
"CHANLIMIT=",
"CHANMODES=b,,,i",
"CHANTYPES=#!",
"CHATHISTORY=100",
# Matrix limit is 64k for the whole event, so this is fairly conservative.
"LINELEN=#{@multiline_max_bytes}",
"MAXTARGETS=1",
# https://github.com/ircv3/ircv3-specifications/pull/510
"MSGREFTYPES=msgid",
Expand Down Expand Up @@ -797,11 +802,28 @@ defmodule M51.IrcConn.Handler do
{"USER", _} ->
nil

{"CAP", ["LS", "302"]} ->
cap_ls(true, send)

{"CAP", ["LS" | _]} ->
cap_ls(false, send)

{"CAP", ["LIST" | _]} ->
send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LIST", "sasl"]})
caps =
M51.IrcConn.State.capabilities(state)
|> Enum.map(fn cap -> @capability_names[cap] end)
|> Enum.filter(fn cap -> !is_nil(cap) end)
|> Enum.join(" ")

send.(%M51.Irc.Command{
source: "server.",
command: "CAP",
params: ["*", "LIST", caps]
})

{"CAP", [subcommand | _]} ->
# ERR_INVALIDCAPCMD
# TODO: support CAP REQ to turn caps on and off post-registration.
send_numeric.("410", [subcommand, "Invalid CAP subcommand"])

{"CAP", []} ->
Expand Down Expand Up @@ -1113,51 +1135,61 @@ defmodule M51.IrcConn.Handler do
[_server, target | _] -> target
end

[local_name, hostname] = String.split(target, ":", parts: 2)
case String.split(target, ":", parts: 2) do
[_] ->
# return ERR_NOSUCHNICK
if target == "" || String.contains?(target, " ") do
send_numeric.("401", ["*", "No such nick"])
else
send_numeric.("401", [target, "No such nick"])
end

[member: memberships] = M51.MatrixClient.State.user(matrix_state, target)
[local_name, hostname] ->
[member: memberships] = M51.MatrixClient.State.user(matrix_state, target)

# TODO: pick the most common display name instead
gecos = target
# TODO: pick the most common display name instead
gecos = target

overhead = make_numeric.("353", [target, ""]) |> M51.Irc.Command.format() |> byte_size()
overhead =
make_numeric.("353", [target, ""]) |> M51.Irc.Command.format() |> byte_size()

first_commands = [
# RPL_WHOISUSER "<nick> <username> <host> * :<realname>"
make_numeric.("311", [target, local_name, hostname, "*", gecos])
]
first_commands = [
# RPL_WHOISUSER "<nick> <username> <host> * :<realname>"
make_numeric.("311", [target, local_name, hostname, "*", gecos])
]

channel_commands =
memberships
|> Map.keys()
|> Enum.map(fn room_id ->
M51.MatrixClient.State.room_irc_channel(matrix_state, room_id)
end)
|> Enum.sort()
|> M51.Irc.WordWrap.join_tokens(512 - overhead)
|> Enum.map(fn line ->
line = line |> String.trim_trailing()

if line != "" do
# RPL_WHOISCHANNELS "<nick> :[prefix]<channel>{ [prefix]<channel>}"
make_numeric.("319", [target, line])
end
end)
|> Enum.filter(fn line -> line != nil end)

last_commands = [
# RPL_WHOISSERVER "<nick> <server> :<server info>"
make_numeric.("312", [target, hostname, hostname]),
# RPL_WHOISACCOUNT "<nick> <account> :is logged in as"
make_numeric.("330", [target, target, "is logged in as"]),
# RPL_ENDOFWHOIS
make_numeric.("318", [target, "End of WHOIS"])
]

send_batch.(
Enum.concat([first_commands, channel_commands, last_commands]),
"labeled-response"
)
channel_commands =
memberships
|> Map.keys()
|> Enum.map(fn room_id ->
M51.MatrixClient.State.room_irc_channel(matrix_state, room_id)
end)
|> Enum.sort()
|> M51.Irc.WordWrap.join_tokens(512 - overhead)
|> Enum.map(fn line ->
line = line |> String.trim_trailing()

if line != "" do
# RPL_WHOISCHANNELS "<nick> :[prefix]<channel>{ [prefix]<channel>}"
make_numeric.("319", [target, line])
end
end)
|> Enum.filter(fn line -> line != nil end)

last_commands = [
# RPL_WHOISSERVER "<nick> <server> :<server info>"
make_numeric.("312", [target, hostname, hostname]),
# RPL_WHOISACCOUNT "<nick> <account> :is logged in as"
make_numeric.("330", [target, target, "is logged in as"]),
# RPL_ENDOFWHOIS
make_numeric.("318", [target, "End of WHOIS"])
]

send_batch.(
Enum.concat([first_commands, channel_commands, last_commands]),
"labeled-response"
)
end

{"BATCH", [first_param | params]} ->
{first_char, reference_tag} = String.split_at(first_param, 1)
Expand Down
9 changes: 8 additions & 1 deletion lib/irc_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ defmodule M51.IrcServer do
end

defp accept(port, retries_left \\ 10) do
case :gen_tcp.listen(port, [:binary, :inet6, packet: :line, active: false, reuseaddr: true]) do
opts = [
:binary, :inet6,
packet: :line,
active: false,
reuseaddr: true,
buffer: M51.IrcConn.Handler.multiline_max_bytes * 2
]
case :gen_tcp.listen(port, opts) do
{:ok, server_sock} ->
Logger.info("Listening on port #{port}")
loop_accept(server_sock)
Expand Down
4 changes: 2 additions & 2 deletions lib/matrix_client/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ defmodule M51.MatrixClient.Client do
# Check the server supports password login
url = base_url <> "/_matrix/client/r0/login"
Logger.debug("(raw) GET #{url}")
response = httpoison.get!(url)
response = httpoison.get!(url, [], timeout: @timeout)
Logger.debug(Kernel.inspect(response))

case response do
Expand Down Expand Up @@ -111,7 +111,7 @@ defmodule M51.MatrixClient.Client do

url = base_url <> "/_matrix/client/r0/login"
Logger.debug("(raw) POST #{url} " <> Kernel.inspect(body))
response = httpoison.post!(url, body)
response = httpoison.post!(url, body, [], timeout: @timeout)
Logger.debug(Kernel.inspect(response))

case response do
Expand Down
30 changes: 21 additions & 9 deletions lib/matrix_client/poller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,9 @@ defmodule M51.MatrixClient.Poller do
end

# Sends self JOIN, RPL_TOPIC/RPL_NOTOPIC, RPL_NAMREPLY
#
# Returns whether the announce was actually sent (ie. if the channel has a canonical
# alias, or was allowed to be sent without a canonical alias)
defp send_channel_welcome(
sup_pid,
room_id,
Expand All @@ -1172,15 +1175,20 @@ defmodule M51.MatrixClient.Poller do

supports_channel_rename = Enum.member?(capabilities, :channel_rename)

if old_canonical_alias == nil || !supports_channel_rename do
announce_new_channel(
M51.IrcConn.Supervisor,
sup_pid,
room_id,
write,
event
)
end
announced_new_channel =
if old_canonical_alias == nil || !supports_channel_rename do
announce_new_channel(
M51.IrcConn.Supervisor,
sup_pid,
room_id,
write,
event
)

true
else
false
end

if old_canonical_alias != nil do
if supports_channel_rename do
Expand All @@ -1197,6 +1205,8 @@ defmodule M51.MatrixClient.Poller do
command: "RENAME",
params: [old_canonical_alias, new_canonical_alias, "Canonical alias changed"]
})

true
else
close_renamed_channel(
sup_pid,
Expand All @@ -1205,6 +1215,8 @@ defmodule M51.MatrixClient.Poller do
canonical_alias_sender,
old_canonical_alias
)

announced_new_channel
end
end
end
Expand Down
7 changes: 5 additions & 2 deletions test/format/common_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,18 @@ defmodule M51.FormatTest do

test "Matrix colors to IRC" do
assert M51.Format.matrix2irc(~s(<font data-mx-color="#FF0000">foo</font>)) ==
"\x04FF0000,FFFFFFfoo\x0399,99"
"\x04FF0000foo\x0399,99"

assert M51.Format.matrix2irc(~s(<font data-mx-color="FF0000">foo</font>)) ==
"\x04FF0000,FFFFFFfoo\x0399,99"
"\x04FF0000foo\x0399,99"

assert M51.Format.matrix2irc(
~s(<font data-mx-color="#FF0000" data-mx-bg-color="00FF00">foo</font>)
) == "\x04FF0000,00FF00foo\x0399,99"

assert M51.Format.matrix2irc(~s(<font data-mx-bg-color="00FF00">foo</font>)) ==
"\x04000000,00FF00\x0399foo\x0399,99"

assert M51.Format.matrix2irc(
~s(<font data-mx-color="#FF0000" data-mx-bg-color="#00FF00">foo) <>
~s(<font data-mx-color="#00FF00" data-mx-bg-color="#0000FF">bar) <>
Expand Down
Loading

0 comments on commit ea1f134

Please sign in to comment.