pec_ruby 0.2.1 → 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,20 +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: 20, reverse: true)
105
-
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
+
106
171
  if messages.empty?
107
- 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)
108
174
  return
109
175
  end
110
176
 
@@ -113,103 +179,290 @@ module PecRuby
113
179
  [label, msg]
114
180
  end
115
181
 
116
- puts "\n" + "─" * 120
117
- puts "MESSAGGI PEC RICEVUTI (più recenti in alto)"
118
- puts "─" * 120
119
-
120
- 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)
121
186
  display_message(selected_message)
122
-
123
187
  rescue PecRuby::Error => e
124
- 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)
125
194
  end
126
195
  end
127
196
 
128
197
  def format_message_label(message)
129
- subject = message.subject || "(nessun oggetto)"
130
- from = message.from || "(mittente sconosciuto)"
131
- date = message.date ? message.date.strftime("%d/%m %H:%M") : "N/A"
132
-
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
+
133
203
  short_subject = subject.length > 60 ? "#{subject[0..56]}..." : subject
134
-
135
- sprintf("%-60s | %-25s | %s",
136
- short_subject,
137
- from.to_s[0..24],
204
+
205
+ format('%-60s | %-25s | %s',
206
+ short_subject,
207
+ from.to_s[0..24],
138
208
  date)
139
209
  end
140
210
 
141
211
  def display_message(message)
142
- puts "\n" + "="*80
143
- puts "MESSAGGIO PEC DECODIFICATO"
144
- puts "="*80
145
-
146
- # Informazioni base PEC
147
- puts "\nINFORMAZIONI CONTENITORE PEC"
148
- puts ""*50
149
- puts sprintf("Oggetto PEC: %s", message.subject || "(nessun oggetto)")
150
- puts sprintf("From PEC: %s", message.from || "(sconosciuto)")
151
- puts sprintf("Data PEC: %s", message.date ? message.date.strftime("%d/%m/%Y %H:%M") : "(sconosciuta)")
152
-
153
- # Informazioni messaggio originale
154
- if message.has_postacert?
155
- puts "\nMESSAGGIO ORIGINALE (da postacert.eml)"
156
- puts "─"*50
157
- puts sprintf("Oggetto: %s", message.original_subject || "(nessun oggetto)")
158
- puts sprintf("Mittente: %s", message.original_from || "(sconosciuto)")
159
- puts sprintf("Destinatari: %s", message.original_to.join(', '))
160
- puts sprintf("Data: %s", message.original_date ? message.original_date.strftime("%d/%m/%Y %H:%M") : "(sconosciuta)")
161
-
162
- # Corpo del messaggio
163
- body = message.original_body
164
- if body && !body.strip.empty?
165
- puts "\nCORPO DEL MESSAGGIO"
166
- puts "─"*50
167
- puts body.strip
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
168
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
169
261
 
170
- # Allegati
171
- attachments = message.original_attachments
172
- puts "\nALLEGATI"
173
- puts "─"*50
174
- if attachments.any?
175
- attachments.each_with_index do |att, i|
176
- puts sprintf("%d. %-30s | %s | %.1f KB",
177
- i+1,
178
- att.filename,
179
- att.mime_type,
180
- att.size_kb)
181
- end
182
-
183
- if @prompt.yes?("\nVuoi scaricare gli allegati?")
184
- download_attachments(attachments)
185
- 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)")
186
338
  else
187
- puts " Nessun allegato presente"
339
+ puts content
188
340
  end
189
341
  else
190
- puts "\nATTENZIONE: Questo messaggio non contiene postacert.eml"
342
+ puts content
191
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)
192
351
 
193
- puts "\n" + "="*80
194
- @prompt.keypress("\nPremi un tasto per continuare...")
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?")
366
+ end
367
+ else
368
+ puts center_text("Nessun allegato presente")
369
+ end
370
+ end
371
+
372
+ def display_info_table(lines)
373
+ max_label_width = lines.map { |line| line[0].length }.max
374
+
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
195
427
  end
196
428
 
197
429
  def download_attachments(attachments)
198
- 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...")
199
441
 
200
442
  begin
201
443
  require 'fileutils'
202
444
  FileUtils.mkdir_p(download_dir)
203
445
 
204
- 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}")
205
451
  file_path = attachment.save_to_dir(download_dir)
206
- puts "Salvato: #{file_path}"
452
+ puts center_text("[+] Salvato: #{file_path}")
207
453
  end
208
454
 
209
- puts "Tutti gli allegati sono stati salvati in #{download_dir}"
210
- rescue => e
211
- 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}")
212
461
  end
462
+
463
+ puts
464
+ puts "═" * @screen_width
465
+ @prompt.keypress(center_text("Premi un tasto per continuare..."), echo: false)
213
466
  end
214
467
  end
215
- end
468
+ end