@@ -27,6 +27,28 @@ func NewStorageRepository(db *sql.DB) domainChatStorage.IChatStorageRepository {
2727 return & SQLiteRepository {db : db }
2828}
2929
30+ // buildContextMetadata marshals whatsmeow ContextInfo-derived fields into a
31+ // JSON blob for the messages.context_metadata column. Returns "" when the
32+ // message has no meaningful context. Shape is open so future fields
33+ // (mentioned_jids, is_forwarded, ...) can be added without a migration.
34+ func buildContextMetadata (ci * waE2E.ContextInfo ) string {
35+ if ci == nil {
36+ return ""
37+ }
38+ meta := map [string ]any {}
39+ if stanzaID := ci .GetStanzaID (); stanzaID != "" {
40+ meta ["replied_to_id" ] = stanzaID
41+ }
42+ if len (meta ) == 0 {
43+ return ""
44+ }
45+ jsonBytes , err := json .Marshal (meta )
46+ if err != nil {
47+ return ""
48+ }
49+ return string (jsonBytes )
50+ }
51+
3052// StoreChat creates or updates a chat
3153func (r * SQLiteRepository ) StoreChat (chat * domainChatStorage.Chat ) error {
3254 now := time .Now ()
@@ -89,7 +111,7 @@ func (r *SQLiteRepository) GetMessageByID(id string) (*domainChatStorage.Message
89111 query := `
90112 SELECT id, chat_jid, device_id, sender, content, timestamp, is_from_me,
91113 media_type, call_metadata, filename, url, media_key, file_sha256,
92- file_enc_sha256, file_length, referral_metadata, created_at, updated_at
114+ file_enc_sha256, file_length, referral_metadata, context_metadata, created_at, updated_at
93115 FROM messages
94116 WHERE id = ?
95117 LIMIT 1
@@ -242,11 +264,11 @@ func (r *SQLiteRepository) StoreMessage(message *domainChatStorage.Message) erro
242264 result , err := r .db .Exec (`
243265 UPDATE messages SET sender = ?, content = ?, timestamp = ?, is_from_me = ?,
244266 media_type = ?, call_metadata = ?, filename = ?, url = ?, media_key = ?, file_sha256 = ?,
245- file_enc_sha256 = ?, file_length = ?, referral_metadata = ?, updated_at = ?
267+ file_enc_sha256 = ?, file_length = ?, referral_metadata = ?, context_metadata = ?, updated_at = ?
246268 WHERE id = ? AND chat_jid = ? AND device_id = ?
247269 ` , message .Sender , message .Content , message .Timestamp , message .IsFromMe ,
248270 message .MediaType , message .CallMetadata , message .Filename , message .URL , message .MediaKey , message .FileSHA256 ,
249- message .FileEncSHA256 , message .FileLength , message .ReferralMetadata , message .UpdatedAt ,
271+ message .FileEncSHA256 , message .FileLength , message .ReferralMetadata , message .ContextMetadata , message . UpdatedAt ,
250272 message .ID , message .ChatJID , message .DeviceID )
251273 if err != nil {
252274 return err
@@ -258,12 +280,12 @@ func (r *SQLiteRepository) StoreMessage(message *domainChatStorage.Message) erro
258280 INSERT INTO messages (
259281 id, chat_jid, device_id, sender, content, timestamp, is_from_me,
260282 media_type, call_metadata, filename, url, media_key, file_sha256,
261- file_enc_sha256, file_length, referral_metadata, created_at, updated_at
262- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
283+ file_enc_sha256, file_length, referral_metadata, context_metadata, created_at, updated_at
284+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
263285 ` , message .ID , message .ChatJID , message .DeviceID , message .Sender , message .Content ,
264286 message .Timestamp , message .IsFromMe , message .MediaType , message .CallMetadata , message .Filename ,
265287 message .URL , message .MediaKey , message .FileSHA256 , message .FileEncSHA256 ,
266- message .FileLength , message .ReferralMetadata , message .CreatedAt , message .UpdatedAt )
288+ message .FileLength , message .ReferralMetadata , message .ContextMetadata , message . CreatedAt , message .UpdatedAt )
267289 }
268290 return err
269291}
@@ -284,7 +306,7 @@ func (r *SQLiteRepository) StoreMessagesBatch(messages []*domainChatStorage.Mess
284306 updateStmt , err := tx .Prepare (`
285307 UPDATE messages SET sender = ?, content = ?, timestamp = ?, is_from_me = ?,
286308 media_type = ?, call_metadata = ?, filename = ?, url = ?, media_key = ?, file_sha256 = ?,
287- file_enc_sha256 = ?, file_length = ?, referral_metadata = ?, updated_at = ?
309+ file_enc_sha256 = ?, file_length = ?, referral_metadata = ?, context_metadata = ?, updated_at = ?
288310 WHERE id = ? AND chat_jid = ? AND device_id = ?
289311 ` )
290312 if err != nil {
@@ -296,8 +318,8 @@ func (r *SQLiteRepository) StoreMessagesBatch(messages []*domainChatStorage.Mess
296318 INSERT INTO messages (
297319 id, chat_jid, device_id, sender, content, timestamp, is_from_me,
298320 media_type, call_metadata, filename, url, media_key, file_sha256,
299- file_enc_sha256, file_length, referral_metadata, created_at, updated_at
300- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
321+ file_enc_sha256, file_length, referral_metadata, context_metadata, created_at, updated_at
322+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
301323 ` )
302324 if err != nil {
303325 return fmt .Errorf ("failed to prepare insert statement: %w" , err )
@@ -316,7 +338,7 @@ func (r *SQLiteRepository) StoreMessagesBatch(messages []*domainChatStorage.Mess
316338 result , err := updateStmt .Exec (
317339 message .Sender , message .Content , message .Timestamp , message .IsFromMe ,
318340 message .MediaType , message .CallMetadata , message .Filename , message .URL , message .MediaKey , message .FileSHA256 ,
319- message .FileEncSHA256 , message .FileLength , message .ReferralMetadata , message .UpdatedAt ,
341+ message .FileEncSHA256 , message .FileLength , message .ReferralMetadata , message .ContextMetadata , message . UpdatedAt ,
320342 message .ID , message .ChatJID , message .DeviceID ,
321343 )
322344 if err != nil {
@@ -329,7 +351,7 @@ func (r *SQLiteRepository) StoreMessagesBatch(messages []*domainChatStorage.Mess
329351 message .ID , message .ChatJID , message .DeviceID , message .Sender , message .Content ,
330352 message .Timestamp , message .IsFromMe , message .MediaType , message .CallMetadata , message .Filename ,
331353 message .URL , message .MediaKey , message .FileSHA256 , message .FileEncSHA256 ,
332- message .FileLength , message .ReferralMetadata , message .CreatedAt , message .UpdatedAt ,
354+ message .FileLength , message .ReferralMetadata , message .ContextMetadata , message . CreatedAt , message .UpdatedAt ,
333355 )
334356 if err != nil {
335357 return fmt .Errorf ("failed to insert message %s: %w" , message .ID , err )
@@ -379,7 +401,7 @@ func (r *SQLiteRepository) GetMessages(filter *domainChatStorage.MessageFilter)
379401 query := `
380402 SELECT id, chat_jid, device_id, sender, content, timestamp, is_from_me,
381403 media_type, call_metadata, filename, url, media_key, file_sha256,
382- file_enc_sha256, file_length, referral_metadata, created_at, updated_at
404+ file_enc_sha256, file_length, referral_metadata, context_metadata, created_at, updated_at
383405 FROM messages
384406 WHERE ` + strings .Join (conditions , " AND " ) + `
385407 ORDER BY timestamp DESC
@@ -444,7 +466,7 @@ func (r *SQLiteRepository) SearchMessages(deviceID, chatJID, searchText string,
444466 query := `
445467 SELECT id, chat_jid, device_id, sender, content, timestamp, is_from_me,
446468 media_type, call_metadata, filename, url, media_key, file_sha256,
447- file_enc_sha256, file_length, referral_metadata, created_at, updated_at
469+ file_enc_sha256, file_length, referral_metadata, context_metadata, created_at, updated_at
448470 FROM messages
449471 WHERE ` + strings .Join (conditions , " AND " ) + `
450472 ORDER BY timestamp DESC
@@ -507,7 +529,7 @@ func (r *SQLiteRepository) scanMessage(scanner interface{ Scan(...any) error })
507529 & message .ID , & message .ChatJID , & message .DeviceID , & message .Sender , & message .Content ,
508530 & message .Timestamp , & message .IsFromMe , & message .MediaType , & message .CallMetadata , & message .Filename ,
509531 & message .URL , & message .MediaKey , & message .FileSHA256 , & message .FileEncSHA256 ,
510- & message .FileLength , & message .ReferralMetadata , & message .CreatedAt , & message .UpdatedAt ,
532+ & message .FileLength , & message .ReferralMetadata , & message .ContextMetadata , & message . CreatedAt , & message .UpdatedAt ,
511533 )
512534 return message , err
513535}
@@ -853,6 +875,12 @@ func (r *SQLiteRepository) CreateMessage(ctx context.Context, evt *events.Messag
853875 }
854876 }
855877
878+ // Capture whatsmeow ContextInfo (reply refs, ...) as a JSON blob so the
879+ // info survives past the webhook event and shows up in /chat/:jid/messages
880+ // responses. Structure is open so additional keys (mentions, forwards)
881+ // can land without a new migration.
882+ contextMetadata := buildContextMetadata (utils .ExtractContextInfo (evt .Message ))
883+
856884 message := & domainChatStorage.Message {
857885 ID : evt .Info .ID ,
858886 ChatJID : chatJID ,
@@ -869,6 +897,7 @@ func (r *SQLiteRepository) CreateMessage(ctx context.Context, evt *events.Messag
869897 FileEncSHA256 : fileEncSHA256 ,
870898 FileLength : fileLength ,
871899 ReferralMetadata : referralMetadata ,
900+ ContextMetadata : contextMetadata ,
872901 }
873902
874903 // Store the message
@@ -1098,22 +1127,31 @@ func (r *SQLiteRepository) StoreSentMessageWithContext(ctx context.Context, mess
10981127 mediaType , filename , mediaURL , mediaKey , fileSHA256 , fileEncSHA256 , fileLength = utils .ExtractMediaInfo (msg )
10991128 }
11001129
1130+ // Capture ContextInfo (reply refs, ...) from the outgoing message too so
1131+ // bot-authored replies show up in /chat/:jid/messages with the same shape
1132+ // as inbound replies. Mirrors the logic in CreateMessage.
1133+ var contextMetadata string
1134+ if msg != nil {
1135+ contextMetadata = buildContextMetadata (utils .ExtractContextInfo (msg ))
1136+ }
1137+
11011138 // Store the sent message
11021139 message := & domainChatStorage.Message {
1103- ID : messageID ,
1104- ChatJID : chatJID ,
1105- DeviceID : deviceID ,
1106- Sender : senderJID ,
1107- Content : content ,
1108- Timestamp : timestamp ,
1109- IsFromMe : true ,
1110- MediaType : mediaType ,
1111- Filename : filename ,
1112- URL : mediaURL ,
1113- MediaKey : mediaKey ,
1114- FileSHA256 : fileSHA256 ,
1115- FileEncSHA256 : fileEncSHA256 ,
1116- FileLength : fileLength ,
1140+ ID : messageID ,
1141+ ChatJID : chatJID ,
1142+ DeviceID : deviceID ,
1143+ Sender : senderJID ,
1144+ Content : content ,
1145+ Timestamp : timestamp ,
1146+ IsFromMe : true ,
1147+ MediaType : mediaType ,
1148+ Filename : filename ,
1149+ URL : mediaURL ,
1150+ MediaKey : mediaKey ,
1151+ FileSHA256 : fileSHA256 ,
1152+ FileEncSHA256 : fileEncSHA256 ,
1153+ FileLength : fileLength ,
1154+ ContextMetadata : contextMetadata ,
11171155 }
11181156
11191157 return r .StoreMessage (message )
@@ -1269,5 +1307,10 @@ func (r *SQLiteRepository) getMigrations() []string {
12691307
12701308 // Migration 16: JSON metadata for Meta Ads referral/attribution (CTWA)
12711309 `ALTER TABLE messages ADD COLUMN referral_metadata TEXT DEFAULT ''` ,
1310+
1311+ // Migration 17: JSON metadata for whatsmeow ContextInfo (reply refs,
1312+ // mentions, forward info). Structure kept open-ended so future fields
1313+ // don't require a new migration — consumers JSON-parse on read.
1314+ `ALTER TABLE messages ADD COLUMN context_metadata TEXT DEFAULT ''` ,
12721315 }
12731316}
0 commit comments