@@ -83,6 +83,13 @@ enum Commands {
8383 #[ arg( long) ]
8484 no_scan : bool ,
8585 } ,
86+ /// Scan all unlocked doors and display messages
87+ #[ command( alias = "scan-all" ) ]
88+ ScanAllOpen {
89+ /// Path to minotari executable (default: minotari.exe)
90+ #[ arg( short, long, default_value = "minotari.exe" ) ]
91+ executable : String ,
92+ } ,
8693}
8794
8895#[ tokio:: main]
@@ -111,6 +118,10 @@ async fn main() -> Result<(), anyhow::Error> {
111118 let client = wallet_client:: BinaryWalletClient :: new ( executable. clone ( ) ) ;
112119 show_day ( day, client, no_scan) . await
113120 }
121+ Commands :: ScanAllOpen { executable } => {
122+ let client = wallet_client:: BinaryWalletClient :: new ( executable. clone ( ) ) ;
123+ scan_all_open ( client) . await
124+ }
114125 }
115126}
116127
@@ -634,6 +645,148 @@ async fn show_day<T: WalletClient>(day: Option<u8>, client: T, no_scan: bool) ->
634645 Ok ( ( ) )
635646}
636647
648+ async fn scan_all_open < T : WalletClient > ( client : T ) -> Result < ( ) , anyhow:: Error > {
649+ println ! ( "🔍 Scanning all unlocked doors...\n " ) ;
650+
651+ let unlocked = load_unlocked_days ( ) ;
652+ if unlocked. is_empty ( ) {
653+ println ! ( "No unlocked doors found. Use 'tari-advent open' to unlock a door first." ) ;
654+ return Ok ( ( ) ) ;
655+ }
656+
657+ // Get all unlocked days sorted
658+ let mut unlocked_days: Vec < u8 > = unlocked
659+ . keys ( )
660+ . filter_map ( |key| {
661+ key. strip_prefix ( "day" )
662+ . and_then ( |day_str| day_str. parse :: < u8 > ( ) . ok ( ) )
663+ } )
664+ . collect ( ) ;
665+ unlocked_days. sort ( ) ;
666+
667+ println ! ( "Found {} unlocked door(s): {:?}\n " , unlocked_days. len( ) , unlocked_days) ;
668+
669+ // Get encrypted data
670+ let encrypted_entries: Vec < & str > = ENCRYPTED_DAYS_CSV . lines ( ) . skip ( 1 ) . collect ( ) ;
671+
672+ // Get wallet directory
673+ let wallet_dir = get_wallet_dir ( ) ?;
674+
675+ for & day in & unlocked_days {
676+ println ! ( "═══════════════════════════════════════════════════════════════" ) ;
677+ println ! ( "📅 Day {}" , day) ;
678+ println ! ( "═══════════════════════════════════════════════════════════════" ) ;
679+
680+ // Get the saved password
681+ let password = match unlocked. get ( & format ! ( "day{}" , day) ) {
682+ Some ( p) => p,
683+ None => {
684+ eprintln ! ( "⚠️ Could not find password for day {}, skipping...\n " , day) ;
685+ continue ;
686+ }
687+ } ;
688+
689+ // Get encrypted data and decrypt to get keys
690+ let encrypted_data = match encrypted_entries. get ( day as usize - 1 ) {
691+ Some ( data) => data,
692+ None => {
693+ eprintln ! ( "⚠️ No encrypted data found for day {}, skipping...\n " , day) ;
694+ continue ;
695+ }
696+ } ;
697+
698+ let ( view_key, spend_key) = match decrypt_keys ( encrypted_data, password) {
699+ Ok ( keys) => keys,
700+ Err ( e) => {
701+ eprintln ! ( "⚠️ Failed to decrypt keys for day {}: {}, skipping...\n " , day, e) ;
702+ continue ;
703+ }
704+ } ;
705+
706+ let wallet_file = wallet_dir. join ( format ! ( "wallet-day-{}.sqlite" , day) ) ;
707+
708+ // Only import if the database doesn't exist
709+ if !wallet_file. exists ( ) {
710+ println ! ( "📂 Importing keys for day {}..." , day) ;
711+ if let Err ( e) = client. import_view_key ( & view_key, & spend_key, password, & wallet_file) . await {
712+ eprintln ! ( "⚠️ Failed to import keys for day {}: {}, skipping...\n " , day, e) ;
713+ continue ;
714+ }
715+ }
716+
717+ println ! ( "🔍 Scanning wallet for day {}..." , day) ;
718+ if let Err ( e) = client. scan ( & wallet_file, "password1" , "https://rpc.tari.com" ) . await {
719+ eprintln ! ( "⚠️ Scan failed for day {}: {}, continuing...\n " , day, e) ;
720+ }
721+
722+ // Construct TariAddress from the keys
723+ match construct_tari_address ( & view_key, & spend_key) {
724+ Ok ( address) => {
725+ let display_address = address. to_base58 ( ) ;
726+ println ! ( "🏦 Wallet Address: {}" , display_address) ;
727+ }
728+ Err ( e) => {
729+ eprintln ! ( "⚠️ Warning: Could not construct Tari address: {}" , e) ;
730+ }
731+ }
732+
733+ // Query the outputs table for messages
734+ println ! ( "\n 📬 Messages:" ) ;
735+ match Connection :: open ( & wallet_file) {
736+ Ok ( conn) => {
737+ match conn. prepare (
738+ "SELECT mined_timestamp, memo_parsed FROM outputs WHERE memo_parsed IS NOT NULL AND memo_parsed != '' ORDER BY mined_timestamp"
739+ ) {
740+ Ok ( mut stmt) => {
741+ match stmt. query_map ( [ ] , |row| {
742+ Ok ( (
743+ row. get :: < _ , String > ( 0 ) ?,
744+ row. get :: < _ , String > ( 1 ) ?,
745+ ) )
746+ } ) {
747+ Ok ( messages) => {
748+ let mut found_any = false ;
749+ for message in messages {
750+ match message {
751+ Ok ( ( timestamp, memo) ) => {
752+ found_any = true ;
753+ println ! ( " [{}] {}" , timestamp, memo) ;
754+ }
755+ Err ( e) => {
756+ eprintln ! ( "⚠️ Error reading message: {}" , e) ;
757+ }
758+ }
759+ }
760+ if !found_any {
761+ println ! ( " (No messages found)" ) ;
762+ }
763+ }
764+ Err ( e) => {
765+ eprintln ! ( "⚠️ Could not query messages: {}" , e) ;
766+ println ! ( " (No messages found or database error)" ) ;
767+ }
768+ }
769+ }
770+ Err ( e) => {
771+ eprintln ! ( "⚠️ Could not prepare query: {}" , e) ;
772+ println ! ( " (No messages found or database error)" ) ;
773+ }
774+ }
775+ conn. close ( ) . ok ( ) ;
776+ }
777+ Err ( e) => {
778+ eprintln ! ( "⚠️ Could not open database: {}" , e) ;
779+ println ! ( " (No messages found or database error)" ) ;
780+ }
781+ }
782+
783+ println ! ( ) ;
784+ }
785+
786+ println ! ( "✅ Scan complete! Scanned {} door(s)." , unlocked_days. len( ) ) ;
787+ Ok ( ( ) )
788+ }
789+
637790fn construct_tari_address (
638791 view_key_hex : & str ,
639792 spend_key_hex : & str ,
0 commit comments