@@ -18,8 +18,9 @@ import (
1818
1919var (
2020 ErrAuthentication = errors .New ("matrix authentication failed" )
21- ErrInvalidRecipient = errors .New ("recipient is not resolvable to a Matrix room" )
21+ ErrInvalidRecipient = errors .New ("recipient is not resolvable to a Matrix user or room" )
2222 ErrMappingNotFound = errors .New ("mapping not found" )
23+ ErrInvalidSender = errors .New ("sender is not resolvable to a Matrix user" )
2324)
2425
2526// MessageService handles sending/fetching messages plus the mapping store.
@@ -48,48 +49,65 @@ func NewMessageService(matrixClient *matrix.MatrixClient) *MessageService {
4849}
4950
5051// SendMessage translates an Acrobits send_message request into Matrix /send.
52+ // Only 1-to-1 direct messaging is supported.
53+ // Both sender and recipient are resolved to Matrix user IDs using local mappings if necessary.
5154func (s * MessageService ) SendMessage (ctx context.Context , req * models.SendMessageRequest ) (* models.SendMessageResponse , error ) {
52- // The user to impersonate is taken from the 'From' field.
53- userID := id .UserID (req .From )
54- if userID == "" {
55- logger .Warn ().Msg ("send message: empty user ID" )
56- return nil , ErrAuthentication
55+ senderStr := strings .TrimSpace (req .From )
56+ if senderStr == "" {
57+ logger .Warn ().Msg ("send message: empty sender" )
58+ return nil , ErrInvalidSender
5759 }
5860
59- // If 'From' is a phone number, try to map it to a Matrix user
60- if isPhoneNumber (req .From ) {
61- logger .Debug ().Str ("from" , req .From ).Msg ("sender is a phone number, attempting to resolve to Matrix user" )
62- if entry , ok := s .getMapping (req .From ); ok && entry .MatrixID != "" {
63- resolvedUserID := id .UserID (entry .MatrixID )
64- logger .Info ().Str ("phone_number" , req .From ).Str ("matrix_id" , entry .MatrixID ).Msg ("phone number mapped to Matrix user" )
65- userID = resolvedUserID
66- } else {
67- logger .Warn ().Str ("phone_number" , req .From ).Msg ("phone number mapping not found, using original sender ID" )
68- }
61+ recipientStr := strings .TrimSpace (req .SMSTo )
62+ if recipientStr == "" {
63+ logger .Warn ().Msg ("send message: empty recipient" )
64+ return nil , ErrInvalidRecipient
6965 }
7066
71- logger .Debug ().Str ("user_id" , string (userID )).Str ("recipient" , req .SMSTo ).Msg ("resolving recipient" )
67+ // Resolve sender to a valid Matrix user ID
68+ sender := s .resolveMatrixUser (senderStr )
69+ if sender == "" {
70+ logger .Warn ().Str ("sender" , senderStr ).Msg ("sender is not a valid Matrix user ID" )
71+ return nil , ErrInvalidSender
72+ }
7273
73- roomID , err := s .resolveRecipient (ctx , userID , req .SMSTo )
74+ // Resolve recipient to a valid Matrix user ID
75+ recipient := s .resolveMatrixUser (recipientStr )
76+ if recipient == "" {
77+ logger .Warn ().Str ("recipient" , recipientStr ).Msg ("recipient is not a valid Matrix user ID" )
78+ return nil , ErrInvalidRecipient
79+ }
80+
81+ logger .Debug ().Str ("sender" , string (sender )).Str ("recipient" , string (recipient )).Msg ("resolved sender and recipient to Matrix user IDs" )
82+
83+ // For 1-to-1 messaging, ensure a direct room exists between sender and recipient
84+ roomID , err := s .ensureDirectRoom (ctx , sender , recipient )
7485 if err != nil {
75- logger .Error ().Str ("user_id " , string (userID )).Str ("recipient" , req . SMSTo ) .Err (err ).Msg ("failed to resolve recipient " )
86+ logger .Error ().Str ("sender " , string (sender )).Str ("recipient" , string ( recipient )) .Err (err ).Msg ("failed to ensure direct room " )
7687 return nil , err
7788 }
7889
79- logger .Debug ().Str ("user_id" , string (userID )).Str ("room_id" , string (roomID )).Msg ("sending message to room" )
90+ logger .Debug ().Str ("sender" , string (sender )).Str ("recipient" , string (recipient )).Str ("room_id" , string (roomID )).Msg ("sending message to direct room" )
91+
92+ // Ensure the sender is a member of the room (in case join failed during room creation)
93+ _ , err = s .matrixClient .JoinRoom (ctx , sender , roomID )
94+ if err != nil {
95+ logger .Error ().Str ("sender" , string (sender )).Str ("room_id" , string (roomID )).Err (err ).Msg ("failed to join room" )
96+ return nil , fmt .Errorf ("send message: %w" , err )
97+ }
8098
8199 content := & event.MessageEventContent {
82100 MsgType : event .MsgText ,
83101 Body : req .SMSBody ,
84102 }
85103
86- resp , err := s .matrixClient .SendMessage (ctx , userID , roomID , content )
104+ resp , err := s .matrixClient .SendMessage (ctx , sender , roomID , content )
87105 if err != nil {
88- logger .Error ().Str ("user_id " , string (userID )).Str ("room_id" , string (roomID )).Err (err ).Msg ("failed to send message" )
106+ logger .Error ().Str ("sender " , string (sender )).Str ("room_id" , string (roomID )).Err (err ).Msg ("failed to send message" )
89107 return nil , fmt .Errorf ("send message: %w" , mapAuthErr (err ))
90108 }
91109
92- logger .Debug ().Str ("user_id " , string (userID )).Str ("room_id" , string (roomID )).Str ("event_id" , string (resp .EventID )).Msg ("message sent successfully" )
110+ logger .Debug ().Str ("sender " , string (sender )).Str ("room_id" , string (roomID )).Str ("event_id" , string (resp .EventID )).Msg ("message sent successfully" )
93111 return & models.SendMessageResponse {SMSID : string (resp .EventID )}, nil
94112}
95113
@@ -134,41 +152,27 @@ func (s *MessageService) FetchMessages(ctx context.Context, req *models.FetchMes
134152 }, nil
135153}
136154
137- func (s * MessageService ) resolveRecipient (ctx context.Context , actingUserID id.UserID , raw string ) (id.RoomID , error ) {
138- trimmed := strings .TrimSpace (raw )
139- if trimmed == "" {
140- logger .Warn ().Str ("user_id" , string (actingUserID )).Msg ("empty recipient" )
141- return "" , ErrInvalidRecipient
142- }
143- // If it looks like a RoomID, use it directly.
144- if strings .HasPrefix (trimmed , "!" ) {
145- logger .Debug ().Str ("user_id" , string (actingUserID )).Str ("room_id" , trimmed ).Msg ("using direct room ID" )
146- return id .RoomID (trimmed ), nil
147- }
148- // If it looks like a UserID, ensure a DM exists and use that room.
149- if strings .HasPrefix (trimmed , "@" ) {
150- logger .Debug ().Str ("user_id" , string (actingUserID )).Str ("target_user" , trimmed ).Msg ("resolving recipient as user ID (DM)" )
151- return s .ensureDirectRoom (ctx , actingUserID , id .UserID (trimmed ))
152- }
153- // If it's a room alias, resolve it.
154- if strings .HasPrefix (trimmed , "#" ) {
155- logger .Debug ().Str ("user_id" , string (actingUserID )).Str ("alias" , trimmed ).Msg ("resolving room alias" )
156- resp , err := s .matrixClient .ResolveRoomAlias (ctx , trimmed )
157- if err != nil {
158- logger .Error ().Str ("user_id" , string (actingUserID )).Str ("alias" , trimmed ).Err (err ).Msg ("failed to resolve room alias" )
159- return "" , fmt .Errorf ("resolve room alias: %w" , err )
160- }
161- logger .Debug ().Str ("user_id" , string (actingUserID )).Str ("alias" , trimmed ).Str ("room_id" , string (resp .RoomID )).Msg ("room alias resolved" )
162- return resp .RoomID , nil
155+ // resolveMatrixUser resolves an identifier to a valid Matrix user ID.
156+ // If the identifier is already a valid Matrix user ID (starts with @), it's returned as-is.
157+ // Otherwise, it tries to look up the identifier in the mapping store (e.g., phone number to user).
158+ // Returns empty string if the identifier cannot be resolved.
159+ func (s * MessageService ) resolveMatrixUser (identifier string ) id.UserID {
160+ identifier = strings .TrimSpace (identifier )
161+
162+ // If it's already a valid Matrix user ID, return it
163+ if strings .HasPrefix (identifier , "@" ) {
164+ return id .UserID (identifier )
163165 }
164- // Otherwise, check our internal mapping for a phone number.
165- logger . Debug (). Str ( "user_id" , string ( actingUserID )). Str ( "identifier" , trimmed ). Msg ( "checking mapping store" )
166- if entry , ok := s .getMapping (trimmed ); ok && entry .RoomID != "" {
167- logger .Debug ().Str ("user_id " , string ( actingUserID )). Str ( " identifier" , trimmed ).Str ("room_id " , string ( entry .RoomID )) .Msg ("mapping found " )
168- return entry .RoomID , nil
166+
167+ // Try to look up in mappings (e.g., phone number to Matrix user )
168+ if entry , ok := s .getMapping (identifier ); ok && entry .MatrixID != "" {
169+ logger .Debug ().Str ("original_identifier " , identifier ).Str ("resolved_user " , entry .MatrixID ) .Msg ("identifier resolved from mapping " )
170+ return id . UserID ( entry .MatrixID )
169171 }
170- logger .Warn ().Str ("user_id" , string (actingUserID )).Str ("identifier" , trimmed ).Msg ("recipient not resolvable" )
171- return "" , ErrInvalidRecipient
172+
173+ // Could not resolve
174+ logger .Warn ().Str ("identifier" , identifier ).Msg ("identifier could not be resolved to a Matrix user ID" )
175+ return ""
172176}
173177
174178func (s * MessageService ) ensureDirectRoom (ctx context.Context , actingUserID , targetUserID id.UserID ) (id.RoomID , error ) {
@@ -234,20 +238,20 @@ func (s *MessageService) setMapping(entry mappingEntry) mappingEntry {
234238 defer s .mu .Unlock ()
235239 entry .UpdatedAt = s .now ()
236240 s .mappings [normalized ] = entry
237- logger .Debug ().Str ("sms_number " , entry .SMSNumber ).Str ("room_id" , string (entry .RoomID )).Msg ("mapping stored" )
241+ logger .Debug ().Str ("key " , entry .SMSNumber ).Str ("room_id" , string (entry .RoomID )).Msg ("mapping stored" )
238242 return entry
239243}
240244
241- // LookupMapping returns the currently stored mapping for a given sms number.
242- func (s * MessageService ) LookupMapping (smsNumber string ) (* models.MappingResponse , error ) {
243- entry , ok := s .getMapping (smsNumber )
245+ // LookupMapping returns the currently stored mapping for a given key (phone number or user pair) .
246+ func (s * MessageService ) LookupMapping (key string ) (* models.MappingResponse , error ) {
247+ entry , ok := s .getMapping (key )
244248 if ! ok {
245249 return nil , ErrMappingNotFound
246250 }
247251 return s .buildMappingResponse (entry ), nil
248252}
249253
250- // ListMappings returns all stored mappings as MappingResponse slices .
254+ // ListMappings returns all stored mappings.
251255func (s * MessageService ) ListMappings () ([]* models.MappingResponse , error ) {
252256 s .mu .RLock ()
253257 defer s .mu .RUnlock ()
@@ -258,7 +262,8 @@ func (s *MessageService) ListMappings() ([]*models.MappingResponse, error) {
258262 return out , nil
259263}
260264
261- // SaveMapping persists a new SMS-to-Matrix mapping via the admin API.
265+ // SaveMapping persists a new mapping via the admin API.
266+ // For 1-to-1 messaging, this maps a key (phone number or identifier) to a direct room.
262267func (s * MessageService ) SaveMapping (req * models.MappingRequest ) (* models.MappingResponse , error ) {
263268 smsNumber := strings .TrimSpace (req .SMSNumber )
264269 if smsNumber == "" {
@@ -326,7 +331,7 @@ func mapAuthErr(err error) error {
326331 if errors .Is (err , ErrAuthentication ) {
327332 return err
328333 }
329- if errors .Is (err , mautrix .MUnknownToken ) || errors .Is (err , mautrix .MMissingToken ) || errors . Is ( err , mautrix . MForbidden ) {
334+ if errors .Is (err , mautrix .MUnknownToken ) || errors .Is (err , mautrix .MMissingToken ) {
330335 return fmt .Errorf ("%w" , ErrAuthentication )
331336 }
332337 return err
0 commit comments