@@ -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,48 @@ 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.
5153func (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
54+ sender := id .UserID (strings .TrimSpace (req .From ))
55+ if sender == "" {
56+ logger .Warn ().Msg ("send message: empty sender" )
57+ return nil , ErrInvalidSender
5758 }
5859
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- }
60+ recipient := id .UserID (strings .TrimSpace (req .SMSTo ))
61+ if recipient == "" {
62+ logger .Warn ().Msg ("send message: empty recipient" )
63+ return nil , ErrInvalidRecipient
6964 }
7065
71- logger .Debug ().Str ("user_id" , string (userID )).Str ("recipient" , req .SMSTo ).Msg ("resolving recipient" )
72-
73- roomID , err := s .resolveRecipient (ctx , userID , req .SMSTo )
66+ // For 1-to-1 messaging, ensure a direct room exists between sender and recipient
67+ roomID , err := s .ensureDirectRoom (ctx , sender , recipient )
7468 if err != nil {
75- logger .Error ().Str ("user_id " , string (userID )).Str ("recipient" , req . SMSTo ) .Err (err ).Msg ("failed to resolve recipient " )
69+ logger .Error ().Str ("sender " , string (sender )).Str ("recipient" , string ( recipient )) .Err (err ).Msg ("failed to ensure direct room " )
7670 return nil , err
7771 }
7872
79- logger .Debug ().Str ("user_id" , string (userID )).Str ("room_id" , string (roomID )).Msg ("sending message to room" )
73+ logger .Debug ().Str ("sender" , string (sender )).Str ("recipient" , string (recipient )).Str ("room_id" , string (roomID )).Msg ("sending message to direct room" )
74+
75+ // Ensure the sender is a member of the room (in case join failed during room creation)
76+ _ , err = s .matrixClient .JoinRoom (ctx , sender , roomID )
77+ if err != nil {
78+ logger .Error ().Str ("sender" , string (sender )).Str ("room_id" , string (roomID )).Err (err ).Msg ("failed to join room" )
79+ return nil , fmt .Errorf ("send message: %w" , err )
80+ }
8081
8182 content := & event.MessageEventContent {
8283 MsgType : event .MsgText ,
8384 Body : req .SMSBody ,
8485 }
8586
86- resp , err := s .matrixClient .SendMessage (ctx , userID , roomID , content )
87+ resp , err := s .matrixClient .SendMessage (ctx , sender , roomID , content )
8788 if err != nil {
88- logger .Error ().Str ("user_id " , string (userID )).Str ("room_id" , string (roomID )).Err (err ).Msg ("failed to send message" )
89+ logger .Error ().Str ("sender " , string (sender )).Str ("room_id" , string (roomID )).Err (err ).Msg ("failed to send message" )
8990 return nil , fmt .Errorf ("send message: %w" , mapAuthErr (err ))
9091 }
9192
92- logger .Debug ().Str ("user_id " , string (userID )).Str ("room_id" , string (roomID )).Str ("event_id" , string (resp .EventID )).Msg ("message sent successfully" )
93+ logger .Debug ().Str ("sender " , string (sender )).Str ("room_id" , string (roomID )).Str ("event_id" , string (resp .EventID )).Msg ("message sent successfully" )
9394 return & models.SendMessageResponse {SMSID : string (resp .EventID )}, nil
9495}
9596
@@ -134,43 +135,6 @@ func (s *MessageService) FetchMessages(ctx context.Context, req *models.FetchMes
134135 }, nil
135136}
136137
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
163- }
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
169- }
170- logger .Warn ().Str ("user_id" , string (actingUserID )).Str ("identifier" , trimmed ).Msg ("recipient not resolvable" )
171- return "" , ErrInvalidRecipient
172- }
173-
174138func (s * MessageService ) ensureDirectRoom (ctx context.Context , actingUserID , targetUserID id.UserID ) (id.RoomID , error ) {
175139 // Use both user IDs to create a consistent mapping key for the DM.
176140 key := fmt .Sprintf ("%s|%s" , actingUserID , targetUserID )
@@ -234,20 +198,20 @@ func (s *MessageService) setMapping(entry mappingEntry) mappingEntry {
234198 defer s .mu .Unlock ()
235199 entry .UpdatedAt = s .now ()
236200 s .mappings [normalized ] = entry
237- logger .Debug ().Str ("sms_number " , entry .SMSNumber ).Str ("room_id" , string (entry .RoomID )).Msg ("mapping stored" )
201+ logger .Debug ().Str ("key " , entry .SMSNumber ).Str ("room_id" , string (entry .RoomID )).Msg ("mapping stored" )
238202 return entry
239203}
240204
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 )
205+ // LookupMapping returns the currently stored mapping for a given key (phone number or user pair) .
206+ func (s * MessageService ) LookupMapping (key string ) (* models.MappingResponse , error ) {
207+ entry , ok := s .getMapping (key )
244208 if ! ok {
245209 return nil , ErrMappingNotFound
246210 }
247211 return s .buildMappingResponse (entry ), nil
248212}
249213
250- // ListMappings returns all stored mappings as MappingResponse slices .
214+ // ListMappings returns all stored mappings.
251215func (s * MessageService ) ListMappings () ([]* models.MappingResponse , error ) {
252216 s .mu .RLock ()
253217 defer s .mu .RUnlock ()
@@ -258,7 +222,8 @@ func (s *MessageService) ListMappings() ([]*models.MappingResponse, error) {
258222 return out , nil
259223}
260224
261- // SaveMapping persists a new SMS-to-Matrix mapping via the admin API.
225+ // SaveMapping persists a new mapping via the admin API.
226+ // For 1-to-1 messaging, this maps a key (phone number or identifier) to a direct room.
262227func (s * MessageService ) SaveMapping (req * models.MappingRequest ) (* models.MappingResponse , error ) {
263228 smsNumber := strings .TrimSpace (req .SMSNumber )
264229 if smsNumber == "" {
@@ -326,7 +291,7 @@ func mapAuthErr(err error) error {
326291 if errors .Is (err , ErrAuthentication ) {
327292 return err
328293 }
329- if errors .Is (err , mautrix .MUnknownToken ) || errors .Is (err , mautrix .MMissingToken ) || errors . Is ( err , mautrix . MForbidden ) {
294+ if errors .Is (err , mautrix .MUnknownToken ) || errors .Is (err , mautrix .MMissingToken ) {
330295 return fmt .Errorf ("%w" , ErrAuthentication )
331296 }
332297 return err
0 commit comments