Module: ActiveMatrix::Protocols::CS::MessageRelationships

Defined in:
lib/active_matrix/protocols/cs/message_relationships.rb

Overview

Handles message relationships (replies, edits, reactions, threads)

Instance Method Summary collapse

Instance Method Details

#edit_message(room_id, event_id, new_content, msgtype: 'm.text', **params) ⇒ Response

Edit an existing message

Examples:

Edit a text message

api.edit_message(room_id, event_id, "Updated message content")

Edit with formatted content

api.edit_message(room_id, event_id, {
  body: "Updated *formatted* message",
  format: "org.matrix.custom.html",
  formatted_body: "Updated <em>formatted</em> message"
})

Parameters:

  • room_id (String)

    The room ID

  • event_id (String)

    The event ID to edit

  • new_content (Hash, String)

    The new message content

  • msgtype (String) (defaults to: 'm.text')

    The message type (defaults to ‘m.text’)

  • params (Hash)

    Additional parameters for send_message_event

Returns:

  • (Response)

    The response from the server



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 94

def edit_message(room_id, event_id, new_content, msgtype: 'm.text', **params)
  new_content = { body: new_content } if new_content.is_a?(String)
  new_content[:msgtype] ||= msgtype

  # Build the edit event content
  content = {
    body: "* #{new_content[:body]}", # Fallback with asterisk prefix
    msgtype: msgtype,
    'm.new_content' => new_content,
    'm.relates_to' => {
      rel_type: 'm.replace',
      event_id: event_id
    }
  }

  # Copy format fields to top level if present
  if new_content[:format]
    content[:format] = new_content[:format]
    content[:formatted_body] = "* #{new_content[:formatted_body]}"
  end

  send_message_event(room_id, 'm.room.message', content, **params)
end

#event_edited?(event) ⇒ Boolean

Check if an event has been edited

Parameters:

  • event (Hash)

    The event to check

Returns:

  • (Boolean)

    True if the event has been edited



282
283
284
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 282

def event_edited?(event)
  event.dig(:unsigned, :'m.relations', :'m.replace').present?
end

#get_aggregated_relations(room_id, event_ids, rel_type: nil, event_type: nil, **params) ⇒ Response

Get aggregated relations for multiple events

Examples:

Get aggregated reactions for multiple events

api.get_aggregated_relations(room_id, event_ids, rel_type: 'm.annotation')

Parameters:

  • room_id (String)

    The room ID

  • event_ids (Array<String>)

    The event IDs to get relations for

  • rel_type (String, nil) (defaults to: nil)

    Filter by specific relationship type

  • event_type (String, nil) (defaults to: nil)

    Filter by specific event type

  • params (Hash)

    Additional body parameters

Returns:

  • (Response)

    The aggregated relations



229
230
231
232
233
234
235
236
237
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 229

def get_aggregated_relations(room_id, event_ids, rel_type: nil, event_type: nil, **params)
  body = {
    event_ids: event_ids,
    rel_type: rel_type,
    event_type: event_type
  }.merge(params).compact

  request(:post, client_api_latest, "/rooms/#{room_id}/aggregations", body: body)
end

#get_edit_history(room_id, event_id, **params) ⇒ Response

Get the edit history of an event

Examples:

Get edit history

api.get_edit_history(room_id, event_id)

Parameters:

  • room_id (String)

    The room ID

  • event_id (String)

    The event ID to get edit history for

  • params (Hash)

    Additional query parameters

Returns:



248
249
250
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 248

def get_edit_history(room_id, event_id, **params)
  get_relations(room_id, event_id, rel_type: 'm.replace', event_type: 'm.room.message', **params)
end

#get_latest_edit_content(event) ⇒ Hash?

Get the latest edit content for an event

Parameters:

  • event (Hash)

    The event to get latest content for

Returns:

  • (Hash, nil)

    The latest content or nil if not edited



290
291
292
293
294
295
296
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 290

def get_latest_edit_content(event)
  edit_event = event.dig(:unsigned, :'m.relations', :'m.replace')
  return nil unless edit_event

  # Return the m.new_content if available, otherwise the content
  edit_event.dig(:content, :'m.new_content') || edit_event[:content]
end

#get_reactions(room_id, event_id, **params) ⇒ Response

Get all reactions for an event

Examples:

Get all reactions

api.get_reactions(room_id, event_id)

Parameters:

  • room_id (String)

    The room ID

  • event_id (String)

    The event ID to get reactions for

  • params (Hash)

    Additional query parameters

Returns:



261
262
263
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 261

def get_reactions(room_id, event_id, **params)
  get_relations(room_id, event_id, rel_type: 'm.annotation', event_type: 'm.reaction', **params)
end

#get_relations(room_id, event_id, rel_type: nil, event_type: nil, from: nil, to: nil, limit: nil, direction: 'b', **params) ⇒ Response

Get related events for a given event

Examples:

Get all relations

api.get_relations(room_id, event_id)

Get only reactions

api.get_relations(room_id, event_id, rel_type: 'm.annotation', event_type: 'm.reaction')

Get thread replies

api.get_relations(room_id, event_id, rel_type: 'm.thread')

Parameters:

  • room_id (String)

    The room ID

  • event_id (String)

    The event ID to get relations for

  • rel_type (String, nil) (defaults to: nil)

    Filter by specific relationship type

  • event_type (String, nil) (defaults to: nil)

    Filter by specific event type

  • from (String, nil) (defaults to: nil)

    Pagination token

  • to (String, nil) (defaults to: nil)

    Pagination token

  • limit (Integer) (defaults to: nil)

    Maximum number of events to return

  • direction (String) (defaults to: 'b')

    Direction of pagination (‘b’ for backwards, ‘f’ for forwards)

  • params (Hash)

    Additional query parameters

Returns:



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 201

def get_relations(room_id, event_id, rel_type: nil, event_type: nil, 
                  from: nil, to: nil, limit: nil, direction: 'b', **params)
  query = {
    from: from,
    to: to,
    limit: limit,
    dir: direction
  }.merge(params).compact

  # Build the appropriate endpoint based on filters
  endpoint = "/rooms/#{room_id}/relations/#{event_id}"
  endpoint += "/#{rel_type}" if rel_type
  endpoint += "/#{event_type}" if rel_type && event_type

  request(:get, client_api_latest, endpoint, query: query)
end

#get_thread_messages(room_id, thread_root_id, **params) ⇒ Response

Get thread messages for a root event

Examples:

Get thread messages

api.get_thread_messages(room_id, thread_root_id)

Parameters:

  • room_id (String)

    The room ID

  • thread_root_id (String)

    The thread root event ID

  • params (Hash)

    Additional query parameters

Returns:



274
275
276
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 274

def get_thread_messages(room_id, thread_root_id, **params)
  get_relations(room_id, thread_root_id, rel_type: 'm.thread', **params)
end

#get_thread_root_id(event) ⇒ String?

Get the thread root ID for an event

Parameters:

  • event (Hash)

    The event to get thread root for

Returns:

  • (String, nil)

    The thread root event ID or nil if not in a thread



311
312
313
314
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 311

def get_thread_root_id(event)
  return nil unless in_thread?(event)
  event.dig(:content, :'m.relates_to', :event_id)
end

#in_thread?(event) ⇒ Boolean

Check if an event is part of a thread

Parameters:

  • event (Hash)

    The event to check

Returns:

  • (Boolean)

    True if the event is part of a thread



302
303
304
305
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 302

def in_thread?(event)
  rel_type = event.dig(:content, :'m.relates_to', :rel_type)
  rel_type == 'm.thread'
end

#remove_reaction(room_id, reaction_event_id, reason: nil, **params) ⇒ Response

Remove a reaction from an event

Examples:

Remove a reaction

api.remove_reaction(room_id, reaction_event_id)

Parameters:

  • room_id (String)

    The room ID

  • reaction_event_id (String)

    The reaction event ID to redact

  • reason (String, nil) (defaults to: nil)

    Optional reason for removing the reaction

  • params (Hash)

    Additional parameters for redact_event

Returns:

  • (Response)

    The response from the server



153
154
155
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 153

def remove_reaction(room_id, reaction_event_id, reason: nil, **params)
  redact_event(room_id, reaction_event_id, reason: reason, **params)
end

#reply_to(room_id, event_id, content, msgtype: 'm.text', **params) ⇒ Response

Send a message as a reply to another event

Examples:

Reply to a message

api.reply_to(room_id, event_id, "I agree!")

Reply with custom msgtype

api.reply_to(room_id, event_id, { url: "mxc://..." }, msgtype: 'm.image')

Parameters:

  • room_id (String)

    The room ID

  • event_id (String)

    The event ID to reply to

  • content (Hash, String)

    The message content

  • msgtype (String) (defaults to: 'm.text')

    The message type (defaults to ‘m.text’)

  • params (Hash)

    Additional parameters for send_message_event

Returns:

  • (Response)

    The response from the server



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 23

def reply_to(room_id, event_id, content, msgtype: 'm.text', **params)
  content = { body: content } if content.is_a?(String)
  content[:msgtype] ||= msgtype

  # Add the reply relationship
  content[:'m.relates_to'] = {
    'm.in_reply_to' => {
      event_id: event_id
    }
  }

  send_message_event(room_id, 'm.room.message', content, **params)
end

#send_reaction(room_id, event_id, key, **params) ⇒ Response

Send a reaction to an event

Examples:

React with an emoji

api.send_reaction(room_id, event_id, "👍")

React with custom text

api.send_reaction(room_id, event_id, "agree")

Parameters:

  • room_id (String)

    The room ID

  • event_id (String)

    The event ID to react to

  • key (String)

    The reaction key (usually an emoji)

  • params (Hash)

    Additional parameters for send_message_event

Returns:

  • (Response)

    The response from the server



131
132
133
134
135
136
137
138
139
140
141
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 131

def send_reaction(room_id, event_id, key, **params)
  content = {
    'm.relates_to' => {
      rel_type: 'm.annotation',
      event_id: event_id,
      key: key
    }
  }

  send_message_event(room_id, 'm.reaction', content, **params)
end

#send_reference(room_id, event_id, content, msgtype: 'm.text', **params) ⇒ Response

Send a reference to another event

Examples:

Send a message referencing another event

api.send_reference(room_id, event_id, "See the above message")

Parameters:

  • room_id (String)

    The room ID

  • event_id (String)

    The event ID to reference

  • content (Hash, String)

    The message content

  • msgtype (String) (defaults to: 'm.text')

    The message type (defaults to ‘m.text’)

  • params (Hash)

    Additional parameters for send_message_event

Returns:

  • (Response)

    The response from the server



168
169
170
171
172
173
174
175
176
177
178
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 168

def send_reference(room_id, event_id, content, msgtype: 'm.text', **params)
  content = { body: content } if content.is_a?(String)
  content[:msgtype] ||= msgtype

  content[:'m.relates_to'] = {
    rel_type: 'm.reference',
    event_id: event_id
  }

  send_message_event(room_id, 'm.room.message', content, **params)
end

#send_threaded_message(room_id, thread_root_id, content, msgtype: 'm.text', latest_event_id: nil, include_fallback: false, **params) ⇒ Response

Send a threaded message

Examples:

Send a threaded message

api.send_threaded_message(room_id, root_event_id, "This is a thread reply")

Send a threaded message with fallback

api.send_threaded_message(room_id, root_id, "Reply", latest_event_id: last_id, include_fallback: true)

Parameters:

  • room_id (String)

    The room ID

  • thread_root_id (String)

    The root event ID of the thread

  • content (Hash, String)

    The message content

  • msgtype (String) (defaults to: 'm.text')

    The message type (defaults to ‘m.text’)

  • latest_event_id (String, nil) (defaults to: nil)

    The latest event in the thread (for fallback)

  • include_fallback (Boolean) (defaults to: false)

    Include reply fallback for older clients

  • params (Hash)

    Additional parameters for send_message_event

Returns:

  • (Response)

    The response from the server



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/active_matrix/protocols/cs/message_relationships.rb', line 53

def send_threaded_message(room_id, thread_root_id, content, msgtype: 'm.text', 
                          latest_event_id: nil, include_fallback: false, **params)
  content = { body: content } if content.is_a?(String)
  content[:msgtype] ||= msgtype

  # Build the thread relationship
  relates_to = {
    rel_type: 'm.thread',
    event_id: thread_root_id
  }

  # Add fallback for older clients if requested
  if include_fallback && latest_event_id
    relates_to[:'m.in_reply_to'] = {
      event_id: latest_event_id
    }
    relates_to[:is_falling_back] = true
  end

  content[:'m.relates_to'] = relates_to
  send_message_event(room_id, 'm.room.message', content, **params)
end