pec_ruby 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/pec_ruby/cli.rb CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  begin
4
4
  require 'tty-prompt'
5
+ require 'tty-screen'
5
6
  require 'awesome_print'
6
7
  rescue LoadError => e
7
- raise LoadError, "CLI dependencies not available. Install with: gem install tty-prompt awesome_print"
8
+ raise LoadError, 'CLI dependencies not available. Install with: gem install tty-prompt tty-screen awesome_print'
8
9
  end
9
10
 
10
11
  module PecRuby
@@ -12,22 +13,29 @@ module PecRuby
12
13
  def initialize
13
14
  @client = nil
14
15
  @prompt = TTY::Prompt.new
16
+ @screen_height = TTY::Screen.height
17
+ @screen_width = TTY::Screen.width
15
18
  end
16
19
 
17
20
  def run
18
- puts banner
19
-
20
21
  loop do
22
+ clear_screen
23
+ show_header
24
+ show_status
25
+
21
26
  case main_menu
22
27
  when :connect
23
28
  connect_to_server
29
+ when :select_folder
30
+ select_folder_menu if connected?
24
31
  when :list_messages
25
32
  list_and_select_messages if connected?
26
33
  when :disconnect
27
34
  disconnect_from_server
28
35
  when :exit
29
36
  disconnect_from_server if connected?
30
- puts "\nArrivederci!"
37
+ clear_screen
38
+ puts center_text('Arrivederci!')
31
39
  break
32
40
  end
33
41
  end
@@ -35,28 +43,56 @@ module PecRuby
35
43
 
36
44
  private
37
45
 
46
+ def clear_screen
47
+ print "\033[2J\033[H"
48
+ end
49
+
50
+ def center_text(text)
51
+ padding = (@screen_width - text.length) / 2
52
+ ' ' * [padding, 0].max + text
53
+ end
54
+
55
+ def show_header
56
+ puts banner
57
+ end
58
+
59
+ def show_status
60
+ if connected?
61
+ status = "CONNESSO: #{@client.username}"
62
+ folder = @client.current_folder ? " | FOLDER: #{@client.current_folder}" : ""
63
+ puts center_text("#{status}#{folder}")
64
+ else
65
+ puts center_text("NON CONNESSO")
66
+ end
67
+ puts "─" * @screen_width
68
+ puts
69
+ end
70
+
38
71
  def banner
39
72
  <<~BANNER
40
-
41
- ╔═══════════════════════════════════════════════════════════════╗
42
- ║ PEC Decoder CLI ║
43
- ║ Decodificatore PEC per Ruby ║
44
- ╚═══════════════════════════════════════════════════════════════╝
45
-
73
+ ██████╗ ███████╗ ██████╗ ██████╗ ██╗ ██╗██████╗ ██╗ ██╗
74
+ ██╔══██╗██╔════╝██╔════╝ ██╔══██╗██║ ██║██╔══██╗╚██╗ ██╔╝
75
+ ██████╔╝█████╗ ██║ ██████╔╝██║ ██║██████╔╝ ╚████╔╝
76
+ ██╔═══╝ ██╔══╝ ██║ ██╔══██╗██║ ██║██╔══██╗ ╚██╔╝
77
+ ██║ ███████╗╚██████╗ ██║ ██║╚██████╔╝██████╔╝ ██║
78
+ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝
79
+
80
+ PEC Ruby CLI v#{PecRuby::VERSION} | Italian PEC Email Manager
46
81
  BANNER
47
82
  end
48
83
 
49
84
  def main_menu
50
85
  choices = []
51
-
86
+
52
87
  if connected?
53
- choices << { name: "Lista e analizza messaggi PEC", value: :list_messages }
54
- choices << { name: "Disconnetti dal server", value: :disconnect }
88
+ choices << { name: 'Seleziona folder', value: :select_folder }
89
+ choices << { name: 'Lista e analizza messaggi', value: :list_messages }
90
+ choices << { name: 'Disconnetti dal server', value: :disconnect }
55
91
  else
56
- choices << { name: "Connetti al server PEC", value: :connect }
92
+ choices << { name: 'Connetti al server PEC', value: :connect }
57
93
  end
58
-
59
- choices << { name: "Esci", value: :exit }
94
+
95
+ choices << { name: 'Esci', value: :exit }
60
96
 
61
97
  @prompt.select("Seleziona un'azione:", choices)
62
98
  end
@@ -64,24 +100,35 @@ module PecRuby
64
100
  def connect_to_server
65
101
  return if connected?
66
102
 
67
- puts "\nCONNESSIONE AL SERVER PEC"
68
- puts "─" * 40
103
+ clear_screen
104
+ show_header
105
+ puts
106
+ puts center_text("CONNESSIONE AL SERVER PEC")
107
+ puts "─" * @screen_width
108
+ puts
69
109
 
70
- host = @prompt.ask("Host IMAP:", default: "imaps.pec.aruba.it")
71
- username = @prompt.ask("Username/Email:")
72
- password = @prompt.mask("Password:")
110
+ host = @prompt.ask('Host IMAP:', default: 'imaps.pec.aruba.it')
111
+ username = @prompt.ask('Username/Email:')
112
+ password = @prompt.mask('Password:')
113
+
114
+ puts
115
+ puts center_text('[*] Connessione in corso...')
73
116
 
74
- print "Connessione in corso..."
75
-
76
117
  begin
77
118
  @client = Client.new(host: host, username: username, password: password)
78
119
  @client.connect
79
- puts " Connesso!"
80
- puts "Connesso come: #{@client.username}"
120
+
121
+ puts center_text('[+] Connesso con successo!')
122
+ puts center_text("Utente: #{@client.username}")
123
+ puts center_text("Folder: INBOX")
124
+
125
+ @prompt.keypress("\n#{center_text('Premi un tasto per continuare...')}", echo: false)
81
126
  rescue PecRuby::Error => e
82
- puts " Errore!"
83
- puts "ATTENZIONE: #{e.message}"
127
+ puts center_text('[-] Errore di connessione!')
128
+ puts center_text("Dettagli: #{e.message}")
84
129
  @client = nil
130
+
131
+ @prompt.keypress("\n#{center_text('Premi un tasto per continuare...')}", echo: false)
85
132
  end
86
133
  end
87
134
 
@@ -91,22 +138,39 @@ module PecRuby
91
138
 
92
139
  def disconnect_from_server
93
140
  return unless connected?
141
+
142
+ clear_screen
143
+ show_header
144
+ puts
145
+ puts center_text('[*] Disconnessione in corso...')
94
146
 
95
147
  @client.disconnect
96
148
  @client = nil
97
- puts "Disconnesso dal server"
149
+
150
+ puts center_text('[+] Disconnesso dal server')
151
+
152
+ @prompt.keypress("\n#{center_text('Premi un tasto per continuare...')}", echo: false)
98
153
  end
99
154
 
100
155
  def list_and_select_messages
101
- puts "\nCaricamento messaggi..."
156
+ current_folder = @client.current_folder || 'INBOX'
102
157
 
158
+ clear_screen
159
+ show_header
160
+ show_status
161
+ puts center_text("MESSAGGI DA #{current_folder.upcase}")
162
+ puts "─" * @screen_width
163
+ puts center_text("[*] Caricamento messaggi...")
164
+
103
165
  begin
104
- messages = @client.pec_messages(limit: 50, reverse: false)
105
- # Sort by date in descending order (newest first)
106
- messages = messages.sort_by { |msg| msg.date || Time.at(0) }.reverse
107
-
166
+ # Get messages in reverse order (newest first) directly from server
167
+ start_time = Time.now
168
+ messages = @client.messages(limit: 50, reverse: true)
169
+ load_time = Time.now - start_time
170
+
108
171
  if messages.empty?
109
- puts "Nessun messaggio PEC trovato"
172
+ puts center_text("[-] Nessun messaggio trovato in #{current_folder}")
173
+ @prompt.keypress("\n#{center_text('Premi un tasto per continuare...')}", echo: false)
110
174
  return
111
175
  end
112
176
 
@@ -115,112 +179,290 @@ module PecRuby
115
179
  [label, msg]
116
180
  end
117
181
 
118
- puts "\n" + "─" * 120
119
- puts "MESSAGGI PEC RICEVUTI (più recenti in alto)"
120
- puts "─" * 120
121
-
122
- selected_message = @prompt.select("Seleziona un messaggio:", choices.to_h, per_page: 15, cycle: true)
182
+ puts center_text("[+] Trovati #{messages.size} messaggi (caricati in #{load_time.round(2)}s)")
183
+ puts
184
+
185
+ selected_message = @prompt.select('Seleziona un messaggio:', choices.to_h, per_page: 15, cycle: true)
123
186
  display_message(selected_message)
124
-
125
187
  rescue PecRuby::Error => e
126
- puts "Errore nel recupero messaggi: #{e.message}"
188
+ puts center_text("[-] Errore nel recupero messaggi: #{e.message}")
189
+ @prompt.keypress("\n#{center_text('Premi un tasto per continuare...')}", echo: false)
190
+ rescue StandardError => e
191
+ puts center_text("[-] Errore imprevisto: #{e.message}")
192
+ puts center_text("Dettagli: #{e.class}")
193
+ @prompt.keypress("\n#{center_text('Premi un tasto per continuare...')}", echo: false)
127
194
  end
128
195
  end
129
196
 
130
197
  def format_message_label(message)
131
- subject = message.subject || "(nessun oggetto)"
132
- from = message.from || "(mittente sconosciuto)"
133
- date = message.date ? message.date.strftime("%d/%m %H:%M") : "N/A"
134
-
198
+ # Use only original (envelope) data for performance - no postacert fetching
199
+ subject = message.original_subject || '(nessun oggetto)'
200
+ from = message.original_from || '(mittente sconosciuto)'
201
+ date = message.original_date ? message.original_date.strftime('%d/%m %H:%M') : 'N/A'
202
+
135
203
  short_subject = subject.length > 60 ? "#{subject[0..56]}..." : subject
136
-
137
- sprintf("%-60s | %-25s | %s",
138
- short_subject,
139
- from.to_s[0..24],
204
+
205
+ format('%-60s | %-25s | %s',
206
+ short_subject,
207
+ from.to_s[0..24],
140
208
  date)
141
209
  end
142
210
 
143
211
  def display_message(message)
144
- puts "\n" + "="*80
145
- puts "MESSAGGIO PEC DECODIFICATO"
146
- puts "="*80
147
-
148
- # Informazioni base PEC
149
- puts "\nINFORMAZIONI CONTENITORE PEC"
150
- puts ""*50
151
- puts sprintf("Oggetto PEC: %s", message.subject || "(nessun oggetto)")
152
- puts sprintf("From PEC: %s", message.from || "(sconosciuto)")
153
- puts sprintf("Data PEC: %s", message.date ? message.date.strftime("%d/%m/%Y %H:%M") : "(sconosciuta)")
154
-
155
- # Informazioni messaggio originale
156
- if message.has_postacert?
157
- puts "\nMESSAGGIO ORIGINALE (da postacert.eml)"
158
- puts "─"*50
159
- puts sprintf("Oggetto: %s", message.original_subject || "(nessun oggetto)")
160
- puts sprintf("Mittente: %s", message.original_from || "(sconosciuto)")
161
- puts sprintf("Destinatari: %s", message.original_to.join(', '))
162
- puts sprintf("Data: %s", message.original_date ? message.original_date.strftime("%d/%m/%Y %H:%M") : "(sconosciuta)")
163
-
164
- # Corpo del messaggio
165
- body = message.original_body
166
- if body && body[:content] && !body[:content].strip.empty?
167
- puts "\nCORPO DEL MESSAGGIO"
168
- puts "─"*50
169
-
170
- # Format the body for better readability
171
- content = body[:content].strip
172
-
173
- # Clean up common formatting issues
174
- content = content.gsub(/\r\n/, "\n") # Normalize line endings
175
- content = content.gsub(/\u0093|\u0094/, '"') # Replace smart quotes
176
- content = content.gsub(/\u0092/, "'") # Replace smart apostrophes
177
-
178
- puts content
212
+ loop do
213
+ clear_screen
214
+ show_header
215
+ show_status
216
+
217
+ # Message header with visual separator
218
+ puts center_text("DETTAGLIO MESSAGGIO")
219
+ puts "" * @screen_width
220
+ puts
221
+
222
+ # Message info in organized sections
223
+ display_message_info(message)
224
+ display_message_body(message, truncate: true)
225
+ display_message_attachments(message, show_download_option: false)
226
+
227
+ puts
228
+ puts "" * @screen_width
229
+
230
+ # Show message menu
231
+ action = show_message_menu(message)
232
+ case action
233
+ when :full_body
234
+ display_full_body(message)
235
+ when :download_attachments
236
+ download_attachments(message.attachments) if message.attachments.any?
237
+ when :back
238
+ break
179
239
  end
240
+ end
241
+ end
242
+
243
+ def show_message_menu(message)
244
+ choices = []
245
+
246
+ # Add body option if message has body
247
+ body = message.raw_body
248
+ if body && body[:content] && !body[:content].strip.empty?
249
+ choices << { name: "Visualizza corpo completo", value: :full_body }
250
+ end
251
+
252
+ # Add attachments option if message has attachments
253
+ if message.attachments.any?
254
+ choices << { name: "Scarica allegati (#{message.attachments.size})", value: :download_attachments }
255
+ end
256
+
257
+ choices << { name: "← Torna alla lista", value: :back }
258
+
259
+ @prompt.select("Seleziona un'azione:", choices)
260
+ end
180
261
 
181
- # Allegati
182
- attachments = message.original_attachments
183
- puts "\nALLEGATI"
184
- puts "─"*50
185
- if attachments.any?
186
- attachments.each_with_index do |att, i|
187
- puts sprintf("%d. %-30s | %s | %.1f KB",
188
- i+1,
189
- att.filename,
190
- att.mime_type,
191
- att.size_kb)
192
- end
193
-
194
- if @prompt.yes?("\nVuoi scaricare gli allegati?")
195
- download_attachments(attachments)
196
- end
262
+ def display_full_body(message)
263
+ clear_screen
264
+ show_header
265
+
266
+ puts center_text("CORPO COMPLETO DEL MESSAGGIO")
267
+ puts "═" * @screen_width
268
+ puts
269
+
270
+ body = message.raw_body
271
+ if body && body[:content] && !body[:content].strip.empty?
272
+ content = body[:content].strip
273
+ content = content.gsub(/\r\n/, "\n")
274
+ content = content.gsub(/\u0093|\u0094/, '"')
275
+ content = content.gsub(/\u0092/, "'")
276
+
277
+ puts content
278
+ else
279
+ puts center_text("Nessun contenuto disponibile")
280
+ end
281
+
282
+ puts
283
+ puts "═" * @screen_width
284
+ @prompt.keypress(center_text("Premi un tasto per continuare..."), echo: false)
285
+ end
286
+
287
+ def display_message_info(message)
288
+ # Main information section
289
+ puts "[INFORMAZIONI PRINCIPALI]"
290
+ puts "─" * (@screen_width - 20)
291
+
292
+ info_lines = [
293
+ ["Oggetto", message.subject || "(nessun oggetto)"],
294
+ ["Mittente", message.from || "(sconosciuto)"],
295
+ ["Destinatari", message.to.join(", ")],
296
+ ["Data", message.date ? message.date.strftime("%d/%m/%Y %H:%M") : "(sconosciuta)"]
297
+ ]
298
+
299
+ display_info_table(info_lines)
300
+ puts
301
+
302
+ # PEC container information
303
+ puts "[INFORMAZIONI CONTENITORE PEC]"
304
+ puts "─" * (@screen_width - 20)
305
+
306
+ pec_lines = [
307
+ ["Oggetto PEC", message.original_subject || "(nessun oggetto)"],
308
+ ["From PEC", message.original_from || "(sconosciuto)"],
309
+ ["Data PEC", message.original_date ? message.original_date.strftime("%d/%m/%Y %H:%M") : "(sconosciuta)"]
310
+ ]
311
+
312
+ display_info_table(pec_lines)
313
+ puts
314
+ end
315
+
316
+ def display_message_body(message, truncate: false)
317
+ body = message.raw_body
318
+ return unless body && body[:content] && !body[:content].strip.empty?
319
+
320
+ puts "[CORPO DEL MESSAGGIO]"
321
+ puts "─" * (@screen_width - 20)
322
+
323
+ # Format the body for better readability
324
+ content = body[:content].strip
325
+ content = content.gsub(/\r\n/, "\n")
326
+ content = content.gsub(/\u0093|\u0094/, '"')
327
+ content = content.gsub(/\u0092/, "'")
328
+
329
+ if truncate
330
+ # Truncate long messages for overview
331
+ max_lines = 10
332
+ lines = content.split("\n")
333
+
334
+ if lines.length > max_lines
335
+ puts lines.first(max_lines).join("\n")
336
+ puts
337
+ puts center_text("... (messaggio troncato, #{lines.length - max_lines} righe rimanenti)")
197
338
  else
198
- puts " Nessun allegato presente"
339
+ puts content
340
+ end
341
+ else
342
+ puts content
343
+ end
344
+ puts
345
+ end
346
+
347
+ def display_message_attachments(message, show_download_option: true)
348
+ attachments = message.attachments
349
+ puts "[ALLEGATI - #{attachments.size}]"
350
+ puts "─" * (@screen_width - 20)
351
+
352
+ if attachments.any?
353
+ attachments.each_with_index do |att, i|
354
+ status = att.filename.include?('postacert') ? '[PEC]' : '[ATT]'
355
+ puts format("%s %2d. %-35s | %-20s | %8.1f KB",
356
+ status,
357
+ i + 1,
358
+ truncate_text(att.filename, 35),
359
+ truncate_text(att.mime_type, 20),
360
+ att.size_kb)
361
+ end
362
+
363
+ if show_download_option
364
+ puts
365
+ download_attachments(attachments) if @prompt.yes?("Vuoi scaricare gli allegati?")
199
366
  end
200
367
  else
201
- puts "\nATTENZIONE: Questo messaggio non contiene postacert.eml"
368
+ puts center_text("Nessun allegato presente")
202
369
  end
370
+ end
371
+
372
+ def display_info_table(lines)
373
+ max_label_width = lines.map { |line| line[0].length }.max
203
374
 
204
- puts "\n" + "="*80
205
- @prompt.keypress("\nPremi un tasto per continuare...")
375
+ lines.each do |label, value|
376
+ formatted_label = label.ljust(max_label_width)
377
+ puts " #{formatted_label} : #{value}"
378
+ end
379
+ end
380
+
381
+ def truncate_text(text, max_length)
382
+ text.length > max_length ? "#{text[0..max_length-4]}..." : text
383
+ end
384
+
385
+ def select_folder_menu
386
+ clear_screen
387
+ show_header
388
+ show_status
389
+ puts center_text("SELEZIONE FOLDER")
390
+ puts "─" * @screen_width
391
+ puts
392
+
393
+ begin
394
+ folders = @client.available_folders
395
+
396
+ if folders.empty?
397
+ puts center_text('[-] Nessuna folder disponibile')
398
+ @prompt.keypress("\n#{center_text('Premi un tasto per continuare...')}", echo: false)
399
+ return
400
+ end
401
+
402
+ current_folder = @client.current_folder || 'INBOX'
403
+ puts center_text("Folder corrente: #{current_folder}")
404
+ puts
405
+
406
+ folder_choices = folders.map { |folder| { name: "#{folder == current_folder ? '[*]' : ' '} #{folder}", value: folder } }
407
+ folder_choices << { name: '← Torna al menu principale', value: :back }
408
+
409
+ selected_folder = @prompt.select('Seleziona una folder:', folder_choices)
410
+
411
+ return if selected_folder == :back
412
+
413
+ if selected_folder != current_folder
414
+ puts center_text("[*] Selezione folder #{selected_folder}...")
415
+ @client.select_folder(selected_folder)
416
+ puts center_text('[+] Folder selezionata con successo!')
417
+ puts center_text("Folder attiva: #{selected_folder}")
418
+ else
419
+ puts center_text("[*] Folder #{selected_folder} già selezionata")
420
+ end
421
+
422
+ @prompt.keypress("\n#{center_text('Premi un tasto per continuare...')}", echo: false)
423
+ rescue PecRuby::Error => e
424
+ puts center_text("[-] Errore nella selezione folder: #{e.message}")
425
+ @prompt.keypress("\n#{center_text('Premi un tasto per continuare...')}", echo: false)
426
+ end
206
427
  end
207
428
 
208
429
  def download_attachments(attachments)
209
- download_dir = @prompt.ask("Directory di download:", default: "./downloads")
430
+ clear_screen
431
+ show_header
432
+ show_status
433
+
434
+ puts center_text("DOWNLOAD ALLEGATI")
435
+ puts "═" * @screen_width
436
+ puts
437
+
438
+ download_dir = @prompt.ask('Directory di download:', default: './downloads')
439
+
440
+ puts center_text("[*] Creazione directory...")
210
441
 
211
442
  begin
212
443
  require 'fileutils'
213
444
  FileUtils.mkdir_p(download_dir)
214
445
 
215
- attachments.each do |attachment|
446
+ puts center_text("[+] Directory creata: #{download_dir}")
447
+ puts
448
+
449
+ attachments.each_with_index do |attachment, i|
450
+ puts center_text("[*] Salvando #{i+1}/#{attachments.size}: #{attachment.filename}")
216
451
  file_path = attachment.save_to_dir(download_dir)
217
- puts "Salvato: #{file_path}"
452
+ puts center_text("[+] Salvato: #{file_path}")
218
453
  end
219
454
 
220
- puts "Tutti gli allegati sono stati salvati in #{download_dir}"
221
- rescue => e
222
- puts "Errore nel salvataggio: #{e.message}"
455
+ puts
456
+ puts center_text("[+] Tutti gli allegati sono stati salvati!")
457
+ puts center_text("Directory: #{download_dir}")
458
+
459
+ rescue StandardError => e
460
+ puts center_text("[-] Errore nel salvataggio: #{e.message}")
223
461
  end
462
+
463
+ puts
464
+ puts "═" * @screen_width
465
+ @prompt.keypress(center_text("Premi un tasto per continuare..."), echo: false)
224
466
  end
225
467
  end
226
- end
468
+ end