9
9
ObjC . import ( 'CoreServices' ) ;
10
10
11
11
// At the top level, after imports
12
- const OUTPUT_FORMAT = {
13
- LINE : '-line' ,
14
- JSON : '-json' ,
15
- CSV : '-csv' ,
16
- COLUMN : '-column' ,
17
- HTML : '-html' ,
18
- INSERT : '-insert' ,
19
- LIST : '-list'
20
- } ;
21
-
22
- // Valid output formats as static array
23
12
const VALID_OUTPUT_FORMATS = [
24
13
'json' , // Default
25
14
'line' ,
50
39
51
40
// Add output format getter
52
41
static get OUTPUT_FORMAT ( ) {
53
- return OUTPUT_FORMAT ;
42
+ return VALID_OUTPUT_FORMATS ;
54
43
}
55
44
56
45
// Add valid formats getter
105
94
return new Date ( timestamp * 1000 ) . toISOString ( ) ;
106
95
}
107
96
108
- static formatOutput ( data , format = OUTPUT_FORMAT . JSON ) {
109
- // If not JSON format, return raw data as tab-delimited rows with headers
110
- if ( format !== OUTPUT_FORMAT . JSON ) {
111
- if ( ! data || ! data . data || ! data . data . messages ) return '' ;
112
-
113
- // Define headers
114
- const headers = [
115
- 'GUID' ,
116
- 'MESSAGE' ,
117
- 'DATE' ,
118
- 'SERVICE' ,
119
- 'SENDER' ,
120
- 'RECEIVER'
121
- ] . join ( '\t' ) ;
122
-
123
- // Format data rows
124
- const rows = data . data . messages . map ( msg => {
125
- const m = msg . message ;
126
- return [
127
- m . guid ,
128
- m . content . text ,
129
- m . timestamps . date ,
130
- m . communication . channel . service ,
131
- m . communication . sender . phone_number || m . communication . sender . email || '' ,
132
- m . communication . receiver . phone_number || m . communication . receiver . email || ''
133
- ] . join ( '\t' ) ;
134
- } ) ;
135
-
136
- // Combine headers and rows
137
- return [ headers , ...rows ] . join ( '\n' ) ;
97
+ static formatOutput ( data , format = 'json' ) {
98
+ // Default to JSON
99
+ if ( ! format || format === 'json' ) {
100
+ return JSON . stringify ( data , null , 2 ) ;
138
101
}
139
-
140
- // Return formatted JSON
141
- return JSON . stringify ( data , null , 2 ) ;
102
+
103
+ // Otherwise pass format directly to sqlite
104
+ return data ;
142
105
}
143
106
}
144
107
192
155
this . pipe = $ . NSPipe . pipe ;
193
156
}
194
157
195
- query ( sql , format = OUTPUT_FORMAT . JSON ) {
158
+ query ( sql , format = 'json' ) {
196
159
try {
197
160
const task = $ . NSTask . alloc . init ;
198
161
task . launchPath = MsgIntelUtils . SQLITE_BIN ;
199
- task . arguments = [ this . dbPath , format , sql ] ;
162
+
163
+ // Pass format directly to sqlite
164
+ task . arguments = [ this . dbPath , `-${ format } ` , sql ] ;
200
165
201
166
const pipe = $ . NSPipe . pipe ;
202
167
task . standardOutput = pipe ;
206
171
const data = pipe . fileHandleForReading . readDataToEndOfFile ;
207
172
const output = $ . NSString . alloc . initWithDataEncoding ( data , $ . NSUTF8StringEncoding ) . js ;
208
173
209
- return format === OUTPUT_FORMAT . JSON ? JSON . parse ( output ) : output ;
174
+ // Only parse if JSON
175
+ return format === 'json' ? JSON . parse ( output ) : output ;
210
176
} catch ( e ) {
211
177
return null ;
212
178
}
231
197
} ;
232
198
}
233
199
234
- getMessages ( format = OUTPUT_FORMAT . JSON ) {
200
+ getMessages ( format = 'json' ) {
235
201
const sql = `SELECT
236
202
m.ROWID, m.guid, m.text, m.service, m.handle_id,
237
203
m.is_from_me, m.destination_caller_id,
250
216
LEFT JOIN chat c ON cmj.chat_id = c.ROWID
251
217
WHERE m.text IS NOT NULL;` ;
252
218
253
- const results = this . query ( sql ) ;
254
-
255
- if ( format !== OUTPUT_FORMAT . JSON ) {
256
- return results ;
257
- }
219
+ // For non-JSON formats, return raw sqlite output
220
+ if ( format !== 'json' ) {
221
+ return this . query ( sql , format ) ;
222
+ }
258
223
259
- if ( ! results ) return [ ] ;
224
+ const messages = this . query ( sql ) ;
225
+ if ( ! messages ) return { data : { messages : [ ] } } ;
260
226
261
-
262
227
return {
263
228
job : {
264
229
job_id : `JOB-${ $ . NSProcessInfo . processInfo . processIdentifier } ` ,
275
240
}
276
241
} ,
277
242
data : {
278
- messages : results ? results . map ( msg => ( {
243
+ messages : messages . map ( msg => ( {
279
244
message : {
280
245
guid : msg . guid ,
281
246
timestamps : {
356
321
ck_record_change_tag : msg . ck_record_change_tag
357
322
}
358
323
}
359
- } ) ) : [ ]
324
+ } ) )
360
325
}
361
326
} ;
362
327
}
388
353
} ;
389
354
}
390
355
391
- getAttachments ( format = OUTPUT_FORMAT . JSON ) {
356
+ getAttachments ( format = 'json' ) {
392
357
const sql = `SELECT
393
358
a.ROWID,
394
359
a.guid,
421
386
LEFT JOIN message m ON maj.message_id = m.ROWID
422
387
ORDER BY a.ROWID ASC;` ;
423
388
424
- const results = this . query ( sql , format ) ;
425
-
426
- if ( format !== OUTPUT_FORMAT . JSON ) {
427
- return results ;
389
+ // For non-JSON formats, return raw sqlite output
390
+ if ( format !== 'json' ) {
391
+ return this . query ( sql , format ) ;
428
392
}
429
393
430
- if ( ! results ) return [ ] ;
394
+ const attachments = this . query ( sql ) ;
395
+ if ( ! attachments ) return { data : { attachments : [ ] } } ;
431
396
432
397
return {
433
398
job : {
444
409
type : "discover"
445
410
}
446
411
} ,
447
- attachments : results . map ( att => ( {
448
- attachment : {
449
- guid : att . guid ,
450
- created_date : MsgIntelUtils . convertAppleDate ( att . created_date ) ,
451
- metadata : {
452
- filename : att . filename ,
453
- mime_type : att . mime_type ,
454
- uti : att . uti ,
455
- transfer_name : att . transfer_name ,
456
- total_bytes : att . total_bytes
457
- } ,
458
- status : {
459
- transfer_state : att . transfer_state ,
460
- is_outgoing : att . is_outgoing ,
461
- is_sticker : att . is_sticker ,
462
- hide_attachment : att . hide_attachment ,
463
- is_commsafety_sensitive : att . is_commsafety_sensitive ,
464
- ck_sync_state : att . ck_sync_state
465
- } ,
466
- message : {
467
- guid : att . guid . substring ( att . guid . indexOf ( '_' , att . guid . indexOf ( '_' ) + 1 ) + 1 ) ,
468
- is_from_me : att . is_from_me ,
469
- communication : MsgIntelUtils . mapCommunication ( att ,
470
- this . handles . byRowId . get ( att . handle_id ) ,
471
- this . handles . byId . get ( att . destination_caller_id ) ) ,
472
- state : {
473
- is_delivered : Boolean ( att . is_delivered ) ,
474
- is_read : Boolean ( att . is_read ) ,
475
- is_sent : Boolean ( att . is_sent ) ,
476
- is_spam : Boolean ( att . is_spam ) ,
477
- is_kt_verified : Boolean ( att . is_kt_verified )
412
+ data : {
413
+ attachments : attachments . map ( att => ( {
414
+ attachment : {
415
+ guid : att . guid ,
416
+ created_date : MsgIntelUtils . convertAppleDate ( att . created_date ) ,
417
+ metadata : {
418
+ filename : att . filename ,
419
+ mime_type : att . mime_type ,
420
+ uti : att . uti ,
421
+ transfer_name : att . transfer_name ,
422
+ total_bytes : att . total_bytes
423
+ } ,
424
+ status : {
425
+ transfer_state : att . transfer_state ,
426
+ is_outgoing : att . is_outgoing ,
427
+ is_sticker : att . is_sticker ,
428
+ hide_attachment : att . hide_attachment ,
429
+ is_commsafety_sensitive : att . is_commsafety_sensitive ,
430
+ ck_sync_state : att . ck_sync_state
431
+ } ,
432
+ message : {
433
+ guid : att . guid . substring ( att . guid . indexOf ( '_' , att . guid . indexOf ( '_' ) + 1 ) + 1 ) ,
434
+ is_from_me : att . is_from_me ,
435
+ communication : MsgIntelUtils . mapCommunication ( att ,
436
+ this . handles . byRowId . get ( att . handle_id ) ,
437
+ this . handles . byId . get ( att . destination_caller_id ) ) ,
438
+ state : {
439
+ is_delivered : Boolean ( att . is_delivered ) ,
440
+ is_read : Boolean ( att . is_read ) ,
441
+ is_sent : Boolean ( att . is_sent ) ,
442
+ is_spam : Boolean ( att . is_spam ) ,
443
+ is_kt_verified : Boolean ( att . is_kt_verified )
444
+ }
478
445
}
479
446
}
480
- }
481
- } ) )
447
+ } ) )
448
+ }
482
449
} ;
483
450
}
484
451
}
499
466
} ;
500
467
}
501
468
502
- searchAll ( inputStr , format = OUTPUT_FORMAT . JSON ) {
469
+ searchAll ( inputStr , format = 'json' ) {
503
470
const escapedInputStr = inputStr . replace ( / _ / g, '\\_' ) . replace ( / % / g, '\\%' ) ;
504
471
505
472
const msgSql = `SELECT
524
491
OR h.id LIKE '%${ escapedInputStr } %'
525
492
OR m.destination_caller_id LIKE '%${ escapedInputStr } %';` ;
526
493
494
+ // For non-JSON formats, return raw sqlite output
495
+ if ( format !== 'json' ) {
496
+ return this . query ( msgSql , format ) ;
497
+ }
498
+
527
499
const messages = this . query ( msgSql ) ;
500
+ if ( ! messages ) return { data : { messages : [ ] } } ;
528
501
529
- const output = {
502
+ return {
530
503
job : {
531
504
job_id : `JOB-${ $ . NSProcessInfo . processInfo . processIdentifier } ` ,
532
505
user : MsgIntelUtils . USER_INFO . username ,
627
600
} ) )
628
601
}
629
602
} ;
630
-
631
- return MsgIntelUtils . formatOutput ( output , format ) ;
632
603
}
633
604
634
605
searchByDate ( startDate , endDate ) {
806
777
} ;
807
778
}
808
779
809
- getHiddenMessages ( format = OUTPUT_FORMAT . JSON ) {
780
+ getHiddenMessages ( format = 'json' ) {
810
781
const sql = `SELECT
811
782
-- Timeline Context
812
783
crm.delete_date,
841
812
WHERE m.text IS NOT NULL
842
813
ORDER BY crm.delete_date DESC;` ;
843
814
844
- const results = this . query ( sql , format ) ;
845
-
846
- if ( format !== OUTPUT_FORMAT . JSON ) {
847
- return results ;
815
+ // For non-JSON formats, return raw sqlite output
816
+ if ( format !== 'json' ) {
817
+ return this . query ( sql , format ) ;
848
818
}
849
819
820
+ const messages = this . query ( sql ) ;
821
+ if ( ! messages ) return { data : { hidden_messages : [ ] } } ;
822
+
850
823
return {
851
824
job : {
852
825
job_id : `JOB-${ $ . NSProcessInfo . processInfo . processIdentifier } ` ,
863
836
}
864
837
} ,
865
838
data : {
866
- hidden_messages : results . map ( msg => ( {
839
+ hidden_messages : messages . map ( msg => ( {
867
840
message : {
868
841
guid : msg . guid ,
869
842
is_from_me : msg . is_from_me ,
912
885
} ;
913
886
}
914
887
915
- getContacts ( format = OUTPUT_FORMAT . JSON ) {
888
+ getContacts ( format = 'json' ) {
916
889
const sql = `
917
890
SELECT
918
891
h.ROWID,
943
916
GROUP BY h.ROWID
944
917
ORDER BY h.id;` ;
945
918
946
- const results = this . query ( sql , OUTPUT_FORMAT . JSON ) ;
919
+ const results = this . query ( sql , format ) ;
947
920
948
- if ( OUTPUT_FORMAT . JSON !== OUTPUT_FORMAT . JSON ) {
921
+ if ( format !== 'json' ) {
949
922
return results ;
950
923
}
951
924
@@ -1105,7 +1078,7 @@ Options:
1105
1078
// Main execution
1106
1079
if ( typeof $ !== 'undefined' ) {
1107
1080
const options = parseArgs ( ) ;
1108
- const format = options . output || OUTPUT_FORMAT . JSON ;
1081
+ const format = options . output || 'json' ;
1109
1082
1110
1083
// Add drafts handler
1111
1084
if ( options . drafts ) {
@@ -1126,7 +1099,13 @@ Options:
1126
1099
1127
1100
if ( options . search ) {
1128
1101
const searchResults = search . searchAll ( options . search , format ) ;
1129
- console . log ( searchResults ) ;
1102
+ // For non-JSON formats, return raw output
1103
+ if ( format !== 'json' ) {
1104
+ console . log ( searchResults ) ;
1105
+ } else {
1106
+ // For JSON (default), stringify the results
1107
+ console . log ( JSON . stringify ( searchResults , null , 2 ) ) ;
1108
+ }
1130
1109
return ;
1131
1110
}
1132
1111
@@ -1152,7 +1131,7 @@ Options:
1152
1131
}
1153
1132
} ;
1154
1133
1155
- if ( format !== OUTPUT_FORMAT . JSON ) {
1134
+ if ( format !== VALID_OUTPUT_FORMATS [ 0 ] ) {
1156
1135
let result = '' ;
1157
1136
for ( const [ key , value ] of Object . entries ( results . data ) ) {
1158
1137
if ( value ) result += value + '\n' ;
@@ -1167,7 +1146,7 @@ Options:
1167
1146
if ( options . hidden ) {
1168
1147
const hidden = new HiddenMessages ( ) ;
1169
1148
const results = hidden . getHiddenMessages ( format ) ;
1170
- if ( format !== OUTPUT_FORMAT . JSON ) {
1149
+ if ( format !== VALID_OUTPUT_FORMATS [ 0 ] ) {
1171
1150
console . log ( results ) ;
1172
1151
} else {
1173
1152
console . log ( JSON . stringify ( results , null , 2 ) ) ;
0 commit comments