Skip to content

Commit ea1f134

Browse files
committed
Merge branch 'main' into pantalaimon
2 parents 37cfc06 + 64ff97c commit ea1f134

File tree

10 files changed

+214
-100
lines changed

10 files changed

+214
-100
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ jobs:
88
fail-fast: false
99
matrix:
1010
include:
11-
- otp: '20' # for some reason, erlef/setup-beam@v1 fails on 19
11+
- otp: '21' # :parse_trans does not support OTP 20 anymore
1212
elixir: '1.7.4'
1313
os: 'ubuntu-20.04'
1414
- otp: '22'
1515
elixir: '1.7.4'
1616
os: 'ubuntu-20.04'
17-
- otp: '20'
17+
- otp: '21'
1818
elixir: '1.9'
1919
os: 'ubuntu-20.04'
2020
- otp: '22'

lib/format/common.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ defmodule M51.Format do
5656
"foo\nbar"
5757
5858
iex> M51.Format.matrix2irc(~s(foo <font data-mx-color="#FF0000">bar</font> baz))
59-
"foo \x04FF0000,FFFFFFbar\x0399,99 baz"
59+
"foo \x04FF0000bar\x0399,99 baz"
6060
"""
6161
def matrix2irc(html, homeserver \\ nil) do
6262
tree = :mochiweb_html.parse("<html>" <> html <> "</html>")

lib/format/matrix2irc.ex

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,19 +109,14 @@ defmodule M51.Format.Matrix2Irc do
109109
transform_children(children, state)
110110

111111
_ ->
112-
fg = String.trim_leading(fg || "000000", "#")
113-
bg = String.trim_leading(bg || "FFFFFF", "#")
112+
fg = fg && String.trim_leading(fg, "#")
113+
bg = bg && String.trim_leading(bg, "#")
114114

115-
restored_colors =
116-
case state.color do
117-
# reset
118-
{nil, nil} -> "\x0399,99"
119-
{fg, bg} -> "\x04#{fg},#{bg}"
120-
end
115+
restored_colors = get_color_code(state.color)
121116

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

124-
~s(\x04#{fg},#{bg}) <>
119+
get_color_code({fg, bg}) <>
125120
transform_children(children, state) <> restored_colors
126121
end
127122
end
@@ -143,6 +138,17 @@ defmodule M51.Format.Matrix2Irc do
143138
transform_children(children, state, char)
144139
end
145140

141+
def get_color_code({fg, bg}) do
142+
case {fg, bg} do
143+
# reset
144+
{nil, nil} -> "\x0399,99"
145+
{fg, nil} -> "\x04#{fg}"
146+
# set both fg and bg, then reset fg
147+
{nil, bg} -> "\x04000000,#{bg}\x0399"
148+
{fg, bg} -> "\x04#{fg},#{bg}"
149+
end
150+
end
151+
146152
defp transform_children(children, state, char \\ "") do
147153
Stream.concat([
148154
[char],

lib/irc/handler.ex

Lines changed: 94 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ defmodule M51.IrcConn.Handler do
3333
# 8kB should be a reasonable limit to remain under the allowed 65kB even
3434
# with large signatures and many escapes.
3535
@multiline_max_bytes 8192
36+
def multiline_max_bytes, do: @multiline_max_bytes
3637

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

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

108+
@capability_names @capabilities
109+
|> Enum.map(fn {name, {atom, _}} -> {atom, name} end)
110+
|> Map.new()
111+
107112
@valid_batch_types ["draft/multiline"]
108113

109114
@doc """
@@ -299,6 +304,22 @@ defmodule M51.IrcConn.Handler do
299304
end
300305
end
301306

307+
defp cap_ls(is_302, send) do
308+
caps = @capabilities_ls
309+
|> Map.to_list()
310+
|> Enum.sort_by(fn {k, _v} -> k end)
311+
|> Enum.map(fn {k, {_, v}} ->
312+
cond do
313+
is_nil(v) -> k
314+
!is_302 -> k
315+
true -> k <> "=" <> v
316+
end
317+
end)
318+
|> Enum.join(" ")
319+
320+
send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LS", caps]})
321+
end
322+
302323
# Handles a connection registration command, ie. only NICK/USER/CAP/AUTHENTICATE.
303324
# Returns nil, {:nick, new_nick}, {:user, new_gecos}, {:authenticate, user_id},
304325
# :got_cap_ls, or :got_cap_end.
@@ -341,30 +362,11 @@ defmodule M51.IrcConn.Handler do
341362
nil
342363

343364
{"CAP", ["LS", "302"]} ->
344-
caps =
345-
@capabilities_ls
346-
|> Map.to_list()
347-
|> Enum.sort_by(fn {k, _v} -> k end)
348-
|> Enum.map(fn {k, {_, v}} ->
349-
case v do
350-
nil -> k
351-
_ -> k <> "=" <> v
352-
end
353-
end)
354-
|> Enum.join(" ")
355-
356-
send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LS", caps]})
365+
cap_ls(true, send)
357366
:got_cap_ls
358367

359368
{"CAP", ["LS" | _]} ->
360-
caps =
361-
@capabilities_ls
362-
|> Map.to_list()
363-
|> Enum.sort_by(fn {k, {_, _v}} -> k end)
364-
|> Enum.map(fn {k, _v} -> k end)
365-
|> Enum.join(" ")
366-
367-
send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LS", caps]})
369+
cap_ls(false, send)
368370
:got_cap_ls
369371

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

805+
{"CAP", ["LS", "302"]} ->
806+
cap_ls(true, send)
807+
808+
{"CAP", ["LS" | _]} ->
809+
cap_ls(false, send)
810+
800811
{"CAP", ["LIST" | _]} ->
801-
send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LIST", "sasl"]})
812+
caps =
813+
M51.IrcConn.State.capabilities(state)
814+
|> Enum.map(fn cap -> @capability_names[cap] end)
815+
|> Enum.filter(fn cap -> !is_nil(cap) end)
816+
|> Enum.join(" ")
817+
818+
send.(%M51.Irc.Command{
819+
source: "server.",
820+
command: "CAP",
821+
params: ["*", "LIST", caps]
822+
})
802823

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

807829
{"CAP", []} ->
@@ -1113,51 +1135,61 @@ defmodule M51.IrcConn.Handler do
11131135
[_server, target | _] -> target
11141136
end
11151137

1116-
[local_name, hostname] = String.split(target, ":", parts: 2)
1138+
case String.split(target, ":", parts: 2) do
1139+
[_] ->
1140+
# return ERR_NOSUCHNICK
1141+
if target == "" || String.contains?(target, " ") do
1142+
send_numeric.("401", ["*", "No such nick"])
1143+
else
1144+
send_numeric.("401", [target, "No such nick"])
1145+
end
11171146

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

1120-
# TODO: pick the most common display name instead
1121-
gecos = target
1150+
# TODO: pick the most common display name instead
1151+
gecos = target
11221152

1123-
overhead = make_numeric.("353", [target, ""]) |> M51.Irc.Command.format() |> byte_size()
1153+
overhead =
1154+
make_numeric.("353", [target, ""]) |> M51.Irc.Command.format() |> byte_size()
11241155

1125-
first_commands = [
1126-
# RPL_WHOISUSER "<nick> <username> <host> * :<realname>"
1127-
make_numeric.("311", [target, local_name, hostname, "*", gecos])
1128-
]
1156+
first_commands = [
1157+
# RPL_WHOISUSER "<nick> <username> <host> * :<realname>"
1158+
make_numeric.("311", [target, local_name, hostname, "*", gecos])
1159+
]
11291160

1130-
channel_commands =
1131-
memberships
1132-
|> Map.keys()
1133-
|> Enum.map(fn room_id ->
1134-
M51.MatrixClient.State.room_irc_channel(matrix_state, room_id)
1135-
end)
1136-
|> Enum.sort()
1137-
|> M51.Irc.WordWrap.join_tokens(512 - overhead)
1138-
|> Enum.map(fn line ->
1139-
line = line |> String.trim_trailing()
1140-
1141-
if line != "" do
1142-
# RPL_WHOISCHANNELS "<nick> :[prefix]<channel>{ [prefix]<channel>}"
1143-
make_numeric.("319", [target, line])
1144-
end
1145-
end)
1146-
|> Enum.filter(fn line -> line != nil end)
1147-
1148-
last_commands = [
1149-
# RPL_WHOISSERVER "<nick> <server> :<server info>"
1150-
make_numeric.("312", [target, hostname, hostname]),
1151-
# RPL_WHOISACCOUNT "<nick> <account> :is logged in as"
1152-
make_numeric.("330", [target, target, "is logged in as"]),
1153-
# RPL_ENDOFWHOIS
1154-
make_numeric.("318", [target, "End of WHOIS"])
1155-
]
1156-
1157-
send_batch.(
1158-
Enum.concat([first_commands, channel_commands, last_commands]),
1159-
"labeled-response"
1160-
)
1161+
channel_commands =
1162+
memberships
1163+
|> Map.keys()
1164+
|> Enum.map(fn room_id ->
1165+
M51.MatrixClient.State.room_irc_channel(matrix_state, room_id)
1166+
end)
1167+
|> Enum.sort()
1168+
|> M51.Irc.WordWrap.join_tokens(512 - overhead)
1169+
|> Enum.map(fn line ->
1170+
line = line |> String.trim_trailing()
1171+
1172+
if line != "" do
1173+
# RPL_WHOISCHANNELS "<nick> :[prefix]<channel>{ [prefix]<channel>}"
1174+
make_numeric.("319", [target, line])
1175+
end
1176+
end)
1177+
|> Enum.filter(fn line -> line != nil end)
1178+
1179+
last_commands = [
1180+
# RPL_WHOISSERVER "<nick> <server> :<server info>"
1181+
make_numeric.("312", [target, hostname, hostname]),
1182+
# RPL_WHOISACCOUNT "<nick> <account> :is logged in as"
1183+
make_numeric.("330", [target, target, "is logged in as"]),
1184+
# RPL_ENDOFWHOIS
1185+
make_numeric.("318", [target, "End of WHOIS"])
1186+
]
1187+
1188+
send_batch.(
1189+
Enum.concat([first_commands, channel_commands, last_commands]),
1190+
"labeled-response"
1191+
)
1192+
end
11611193

11621194
{"BATCH", [first_param | params]} ->
11631195
{first_char, reference_tag} = String.split_at(first_param, 1)

lib/irc_server.ex

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,14 @@ defmodule M51.IrcServer do
4040
end
4141

4242
defp accept(port, retries_left \\ 10) do
43-
case :gen_tcp.listen(port, [:binary, :inet6, packet: :line, active: false, reuseaddr: true]) do
43+
opts = [
44+
:binary, :inet6,
45+
packet: :line,
46+
active: false,
47+
reuseaddr: true,
48+
buffer: M51.IrcConn.Handler.multiline_max_bytes * 2
49+
]
50+
case :gen_tcp.listen(port, opts) do
4451
{:ok, server_sock} ->
4552
Logger.info("Listening on port #{port}")
4653
loop_accept(server_sock)

lib/matrix_client/client.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ defmodule M51.MatrixClient.Client do
7878
# Check the server supports password login
7979
url = base_url <> "/_matrix/client/r0/login"
8080
Logger.debug("(raw) GET #{url}")
81-
response = httpoison.get!(url)
81+
response = httpoison.get!(url, [], timeout: @timeout)
8282
Logger.debug(Kernel.inspect(response))
8383

8484
case response do
@@ -111,7 +111,7 @@ defmodule M51.MatrixClient.Client do
111111

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

117117
case response do

lib/matrix_client/poller.ex

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,9 @@ defmodule M51.MatrixClient.Poller do
11571157
end
11581158

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

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

1175-
if old_canonical_alias == nil || !supports_channel_rename do
1176-
announce_new_channel(
1177-
M51.IrcConn.Supervisor,
1178-
sup_pid,
1179-
room_id,
1180-
write,
1181-
event
1182-
)
1183-
end
1178+
announced_new_channel =
1179+
if old_canonical_alias == nil || !supports_channel_rename do
1180+
announce_new_channel(
1181+
M51.IrcConn.Supervisor,
1182+
sup_pid,
1183+
room_id,
1184+
write,
1185+
event
1186+
)
1187+
1188+
true
1189+
else
1190+
false
1191+
end
11841192

11851193
if old_canonical_alias != nil do
11861194
if supports_channel_rename do
@@ -1197,6 +1205,8 @@ defmodule M51.MatrixClient.Poller do
11971205
command: "RENAME",
11981206
params: [old_canonical_alias, new_canonical_alias, "Canonical alias changed"]
11991207
})
1208+
1209+
true
12001210
else
12011211
close_renamed_channel(
12021212
sup_pid,
@@ -1205,6 +1215,8 @@ defmodule M51.MatrixClient.Poller do
12051215
canonical_alias_sender,
12061216
old_canonical_alias
12071217
)
1218+
1219+
announced_new_channel
12081220
end
12091221
end
12101222
end

test/format/common_test.exs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,18 @@ defmodule M51.FormatTest do
6363

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

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

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

75+
assert M51.Format.matrix2irc(~s(<font data-mx-bg-color="00FF00">foo</font>)) ==
76+
"\x04000000,00FF00\x0399foo\x0399,99"
77+
7578
assert M51.Format.matrix2irc(
7679
~s(<font data-mx-color="#FF0000" data-mx-bg-color="#00FF00">foo) <>
7780
~s(<font data-mx-color="#00FF00" data-mx-bg-color="#0000FF">bar) <>

0 commit comments

Comments
 (0)