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 Aug 19, 2023
2 parents 50db541 + bcbcf5d commit 37cfc06
Show file tree
Hide file tree
Showing 10 changed files with 549 additions and 137 deletions.
45 changes: 44 additions & 1 deletion lib/irc/command.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
##
# Copyright (C) 2021-2022 Valentin Lorentz
# Copyright (C) 2021-2023 Valentin Lorentz
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License version 3,
Expand Down Expand Up @@ -251,6 +251,8 @@ defmodule M51.Irc.Command do
"""
def downgrade(command, capabilities) do
original_tags = command.tags

# downgrade echo-message
command =
if Enum.member?(capabilities, :echo_message) do
Expand Down Expand Up @@ -319,6 +321,47 @@ defmodule M51.Irc.Command do
nil
end

%{command: "REDACT"} ->
if Enum.member?(capabilities, :message_redaction) do
command
else
sender = Map.get(original_tags, "account")

display_name =
case Map.get(original_tags, "+draft/display-name", nil) do
dn when is_binary(dn) -> " (#{dn})"
_ -> ""
end

tags = Map.drop(command.tags, ["+draft/display-name", "account"])

command =
case command do
%{params: [channel, msgid, reason]} ->
%M51.Irc.Command{
tags: Map.put(tags, "+draft/reply", msgid),
source: "server.",
command: "NOTICE",
params: [channel, "#{sender}#{display_name} deleted an event: #{reason}"]
}

%{params: [channel, msgid]} ->
%M51.Irc.Command{
tags: Map.put(tags, "+draft/reply", msgid),
source: "server.",
command: "NOTICE",
params: [channel, "#{sender}#{display_name} deleted an event"]
}

_ ->
# shouldn't happen
nil
end

# run downgrade() recursively in order to drop the new tags if necessary
downgrade(command, capabilities)
end

%{command: "TAGMSG"} ->
if Enum.member?(capabilities, :message_tags) do
command
Expand Down
98 changes: 96 additions & 2 deletions lib/irc/handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,24 @@ defmodule M51.IrcConn.Handler do
# https://ircv3.net/specs/extensions/multiline
"draft/multiline" => {:multiline, "max-bytes=#{@multiline_max_bytes}"},

# https://github.com/progval/ircv3-specifications/blob/redaction/extensions/message-redaction.md
"draft/message-redaction" => {:message_redaction, nil},

# https://github.com/ircv3/ircv3-specifications/pull/527
"draft/no-implicit-names" => {:no_implicit_names, nil},

# https://ircv3.net/specs/extensions/sasl-3.1
"sasl" => {:sasl, "PLAIN"},

# https://github.com/ircv3/ircv3-specifications/pull/520
"draft/sasl-ir" => {:sasl_ir, nil},

# https://ircv3.net/specs/extensions/server-time
"server-time" => {:server_time, nil},

# https://ircv3.net/specs/extensions/standard-replies
"standard-replies" => {:standard_replies, nil},

# https://ircv3.net/specs/extensions/userhost-in-names
# not really useful; but kiwiirc/irc-framework interprets "foo:example.org"
# as {nick: '', user: '', hostname: 'foo:example.org'} without this,
Expand Down Expand Up @@ -398,10 +410,16 @@ defmodule M51.IrcConn.Handler do

nil

{"AUTHENTICATE", ["PLAIN" | _]} ->
{"AUTHENTICATE", ["PLAIN"]} ->
send.(%M51.Irc.Command{command: "AUTHENTICATE", params: ["+"]})
nil

{"AUTHENTICATE", ["PLAIN" | params]} ->
# SASL-IR: https://github.com/ircv3/ircv3-specifications/pull/520
# Call this function recursively without the mechanism, to be handled
# by the next case below
handle_connreg(sup_pid, %{command | params: params}, nick)

{"AUTHENTICATE", [param | _]} ->
# this catches both invalid mechs and actual PLAIN message.
# FIXME: add some state to tell the two apart.
Expand Down Expand Up @@ -599,7 +617,7 @@ defmodule M51.IrcConn.Handler do
"CLIENTTAGDENY=*,-draft/react,-draft/reply",
"CHANLIMIT=",
"CHANTYPES=#!",
"CHATHISTORY=1000",
"CHATHISTORY=100",
"MAXTARGETS=1",
# https://github.com/ircv3/ircv3-specifications/pull/510
"MSGREFTYPES=msgid",
Expand Down Expand Up @@ -903,6 +921,27 @@ defmodule M51.IrcConn.Handler do
{"TAGMSG", _} ->
send_needmoreparams.()

{"REDACT", [channel, targetmsgid, reason | _]} ->
send_redact(
sup_pid,
channel,
Map.get(command.tags, "label"),
targetmsgid,
reason
)

{"REDACT", [channel, targetmsgid | _]} ->
send_redact(
sup_pid,
channel,
Map.get(command.tags, "label"),
targetmsgid,
nil
)

{"REDACT", _} ->
send_needmoreparams.()

{"CHATHISTORY", ["TARGETS", _ts1, _ts2, _limit | _]} ->
# This is mainly used for PMs, and we don't support those yet; so there
# is little point in storing state to actually implement it
Expand Down Expand Up @@ -1374,6 +1413,61 @@ defmodule M51.IrcConn.Handler do
end
end

defp send_redact(sup_pid, channel, label, targetmsgid, reason) do
writer = M51.IrcConn.Supervisor.writer(sup_pid)
matrix_client = M51.IrcConn.Supervisor.matrix_client(sup_pid)
matrix_state = M51.IrcConn.Supervisor.matrix_state(sup_pid)
send = fn cmd -> M51.IrcConn.Writer.write_command(writer, cmd) end

# If the client provided a label, use it as txnId on Matrix's side.
# This way we can parse it when receiving the echo from Matrix's event
# stream instead of storing state.
# Otherwise, generate a random transaction id.

nicklist =
case M51.MatrixClient.State.room_from_irc_channel(matrix_state, channel) do
{_room_id, room} -> room.members |> Map.keys()
nil -> []
end

reason =
case reason do
nil ->
nil

reason ->
{reason, _formatted_reason} = M51.Format.irc2matrix(reason, nicklist)
reason
end

result =
M51.MatrixClient.Client.send_redact(
matrix_client,
channel,
label,
targetmsgid,
reason
)

case result do
{:ok, _event_id} ->
nil

{:error, error} ->
send.(%M51.Irc.Command{
source: "server.",
command: "FAIL",
params: [
"REDACT",
"UNKNOWN_ERROR",
channel,
targetmsgid,
"Error while redacting message: " <> Kernel.inspect(error)
]
})
end
end

defp close_connection(sup_pid) do
writer = M51.IrcConn.Supervisor.writer(sup_pid)
M51.IrcConn.Writer.close(writer)
Expand Down
6 changes: 3 additions & 3 deletions lib/matrix/raw_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule M51.Matrix.RawClient do

def get(client, path, headers \\ [], options \\ []) do
headers = [Authorization: "Bearer " <> client.access_token] ++ headers
options = options |> Keyword.put_new(:timeout, 20000)
options = options |> Keyword.put_new(:timeout, 60000)

url = client.base_url <> path

Expand All @@ -47,7 +47,7 @@ defmodule M51.Matrix.RawClient do

def post(client, path, body, headers \\ [], options \\ []) do
headers = [Authorization: "Bearer " <> client.access_token] ++ headers
options = options |> Keyword.put_new(:timeout, 20000)
options = options |> Keyword.put_new(:timeout, 60000)

url = client.base_url <> path

Expand All @@ -71,7 +71,7 @@ defmodule M51.Matrix.RawClient do

def put(client, path, body, headers \\ [], options \\ []) do
headers = [Authorization: "Bearer " <> client.access_token] ++ headers
options = options |> Keyword.put_new(:timeout, 20000)
options = options |> Keyword.put_new(:timeout, 60000)

url = client.base_url <> path

Expand Down
64 changes: 55 additions & 9 deletions lib/matrix_client/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ defmodule M51.MatrixClient.Client do

# timeout used for all requests sent to a homeserver.
# It should be slightly larger than M51.Matrix.RawClient's timeout,
@timeout 25000
@timeout 65000

def start_link(opts) do
{sup_pid, _extra_args} = opts
Expand Down Expand Up @@ -102,7 +102,10 @@ defmodule M51.MatrixClient.Client do
body =
Jason.encode!(%{
"type" => "m.login.password",
"user" => local_name,
"identifier" => %{
"type" => "m.id.user",
"user" => local_name
},
"password" => password
})

Expand Down Expand Up @@ -147,9 +150,7 @@ defmodule M51.MatrixClient.Client do

%HTTPoison.Response{status_code: status_code} ->
message =
"Could not reach the Matrix homeserver for #{hostname}, #{url} returned HTTP #{
status_code
}. Make sure this is a Matrix homeserver and https://#{hostname}/.well-known/matrix/client is properly configured."
"Could not reach the Matrix homeserver for #{hostname}, #{url} returned HTTP #{status_code}. Make sure this is a Matrix homeserver and https://#{hostname}/.well-known/matrix/client is properly configured."

{:reply, {:error, :unknown, message}, state}
end
Expand Down Expand Up @@ -298,6 +299,42 @@ defmodule M51.MatrixClient.Client do
end
end

@impl true
def handle_call({:send_redact, channel, label, event_id, reason}, _from, state) do
%M51.MatrixClient.Client{
state: :connected,
irc_pid: irc_pid,
raw_client: raw_client
} = state

matrix_state = M51.IrcConn.Supervisor.matrix_state(irc_pid)

transaction_id = label_to_transaction_id(label)

reply =
case M51.MatrixClient.State.room_from_irc_channel(matrix_state, channel) do
nil ->
{:reply, {:error, {:room_not_found, channel}}, state}

{room_id, _room} ->
path =
"/_matrix/client/r0/rooms/#{urlquote(room_id)}/redact/#{urlquote(event_id)}/#{transaction_id}"

body =
case reason do
reason when is_binary(reason) -> Jason.encode!(%{"reason" => reason})
_ -> Jason.encode!({})
end

case M51.Matrix.RawClient.put(raw_client, path, body) do
{:ok, %{"event_id" => event_id}} -> {:ok, event_id}
{:error, nil, error} -> {:error, error}
end
end

{:reply, reply, state}
end

@impl true
def handle_call({:get_event_context, channel, event_id, limit}, _from, state) do
%M51.MatrixClient.Client{
Expand All @@ -320,7 +357,7 @@ defmodule M51.MatrixClient.Client do

case M51.Matrix.RawClient.get(raw_client, path) do
{:ok, events} -> {:ok, events}
{:error, error} -> {:error, error}
{:error, nil, error} -> {:error, error}
end
end

Expand Down Expand Up @@ -349,7 +386,7 @@ defmodule M51.MatrixClient.Client do

case M51.Matrix.RawClient.get(raw_client, path) do
{:ok, events} -> {:ok, events}
{:error, error} -> {:error, error}
{:error, nil, error} -> {:error, error}
end
end

Expand Down Expand Up @@ -487,7 +524,6 @@ defmodule M51.MatrixClient.Client do
)

base_url

end
end

Expand Down Expand Up @@ -546,13 +582,23 @@ defmodule M51.MatrixClient.Client do
@doc """
Sends the given event object.
If 'label' is not nil, it will be passed as a 'label' message tagt when
If 'label' is not nil, it will be passed as a 'label' message tag when
the event is seen in the event stream.
"""
def send_event(pid, channel, label, event_type, event) do
GenServer.call(pid, {:send_event, channel, event_type, label, event}, @timeout)
end

@doc """
Asks the server to redact the event with the given id
If 'label' is not nil, it will be passed as a 'label' message tag when
the event is seen in the event stream.
"""
def send_redact(pid, channel, label, event_id, reason) do
GenServer.call(pid, {:send_redact, channel, label, event_id, reason}, @timeout)
end

@doc """
Returns events that happened just before or after the specified event_id.
Expand Down
Loading

0 comments on commit 37cfc06

Please sign in to comment.