Class: Heathrow::MessageOrganizer
- Inherits:
-
Object
- Object
- Heathrow::MessageOrganizer
- Defined in:
- lib/heathrow/message_organizer.rb
Overview
Organizes messages into threads, groups, and channels
Instance Attribute Summary collapse
-
#channels ⇒ Object
readonly
Returns the value of attribute channels.
-
#groups ⇒ Object
readonly
Returns the value of attribute groups.
-
#messages ⇒ Object
readonly
Returns the value of attribute messages.
-
#threads ⇒ Object
readonly
Returns the value of attribute threads.
Instance Method Summary collapse
-
#get_organized_view(sort_order = nil, sort_inverted = false) ⇒ Object
Get organized view of messages.
-
#get_plugin_type(msg) ⇒ Object
Get plugin type for a message (from source_type or by looking up source).
-
#initialize(messages = [], db = nil, group_by_folder: false) ⇒ MessageOrganizer
constructor
A new instance of MessageOrganizer.
-
#is_email_message?(msg) ⇒ Boolean
Detect if message is an email based on metadata.
-
#organize_messages ⇒ Object
Organize messages into logical structures.
Constructor Details
#initialize(messages = [], db = nil, group_by_folder: false) ⇒ MessageOrganizer
Returns a new instance of MessageOrganizer.
9 10 11 12 13 14 15 16 17 18 19 20 |
# File 'lib/heathrow/message_organizer.rb', line 9 def initialize( = [], db = nil, group_by_folder: false) @messages = @db = db @group_by_folder = group_by_folder @threads = {} @groups = {} @channels = {} @dms = [] # Pre-populate source type cache in one query instead of per-message lookups @source_types_cache = db ? db.get_source_type_map : {} end |
Instance Attribute Details
#channels ⇒ Object (readonly)
Returns the value of attribute channels.
7 8 9 |
# File 'lib/heathrow/message_organizer.rb', line 7 def channels @channels end |
#groups ⇒ Object (readonly)
Returns the value of attribute groups.
7 8 9 |
# File 'lib/heathrow/message_organizer.rb', line 7 def groups @groups end |
#messages ⇒ Object (readonly)
Returns the value of attribute messages.
7 8 9 |
# File 'lib/heathrow/message_organizer.rb', line 7 def @messages end |
#threads ⇒ Object (readonly)
Returns the value of attribute threads.
7 8 9 |
# File 'lib/heathrow/message_organizer.rb', line 7 def threads @threads end |
Instance Method Details
#get_organized_view(sort_order = nil, sort_inverted = false) ⇒ Object
Get organized view of messages
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/heathrow/message_organizer.rb', line 108 def get_organized_view(sort_order = nil, sort_inverted = false) organized = [] # Add channels/groups with their messages @channels.each do |channel_id, channel_data| organized << { type: 'channel', name: channel_data[:name], source: channel_data[:source], messages: channel_data[:messages], collapsed: channel_data[:collapsed] || false, unread_count: count_unread(channel_data[:messages]), display_name: channel_data[:display_name] # Pass display_name for Discord channels } end # Add DMs separately first (they go at the top or bottom) dm_section = nil unless @dms.empty? dm_section = { type: 'dm_section', name: 'Direct Messages', messages: @dms, collapsed: false, unread_count: count_unread(@dms) } end # Add threaded messages (emails, forums) to the main list @threads.each do |thread_id, thread_data| next if thread_data[:in_channel] # Skip if already in a channel organized << { type: 'thread', subject: thread_data[:subject], messages: thread_data[:messages], collapsed: thread_data[:collapsed] || false, unread_count: count_unread(thread_data[:messages]) } end # Sort based on sort_order case sort_order when 'alphabetical' organized.sort! do |a, b| clean_a = section_display_name(a).gsub(/^[#\[\]@\s]+/, '') clean_b = section_display_name(b).gsub(/^[#\[\]@\s]+/, '') clean_a == clean_b ? section_display_name(a) <=> section_display_name(b) : clean_a <=> clean_b end when 'unread' organized.sort! do |a, b| cmp = b[:unread_count].to_i <=> a[:unread_count].to_i cmp != 0 ? cmp : section_display_name(a) <=> section_display_name(b) end when 'latest' organized.sort! do |a, b| newest_a = (a[:messages] || []).map { |m| m['timestamp'].to_i }.max || 0 newest_b = (b[:messages] || []).map { |m| m['timestamp'].to_i }.max || 0 newest_b <=> newest_a end when 'source' organized.sort! do |a, b| sa = a[:source] || (a[:type] == 'thread' ? 'email' : 'unknown') sb = b[:source] || (b[:type] == 'thread' ? 'email' : 'unknown') cmp = sa.to_s <=> sb.to_s cmp != 0 ? cmp : section_display_name(a) <=> section_display_name(b) end end # Add DM section(s) at the end (or beginning if inverted) if dm_section if sort_order == 'conversation' # Split DMs into per-conversation sections convos = {} dm_section[:messages].each do |msg| = (msg['metadata']) key = ['thread_id'] || msg['sender'] || 'Unknown' convos[key] ||= { name: msg['sender'] || msg['subject'] || 'Unknown', messages: [] } convos[key][:messages] << msg end conv_sections = convos.map do |_key, data| { type: 'dm_section', name: data[:name], messages: data[:messages], collapsed: false, unread_count: count_unread(data[:messages]) } end # Sort conversation sections alphabetically by name conv_sections.sort_by! { |s| s[:name].to_s.downcase } if sort_inverted organized.unshift(*conv_sections.reverse) else organized.push(*conv_sections) end else if sort_inverted organized.unshift(dm_section) # Add at beginning else organized.push(dm_section) # Add at end end end end # Sort messages within each section to match sort order if sort_order == 'latest' || sort_order == 'conversation' organized.each do |section| next unless section[:messages] section[:messages].sort_by! { |m| -(m['timestamp'].to_i) } end end # Apply invert if requested - reverse everything if sort_inverted organized.reverse! organized.each do |section| next unless section[:messages] section[:messages].reverse! end end organized end |
#get_plugin_type(msg) ⇒ Object
Get plugin type for a message (from source_type or by looking up source)
23 24 25 26 |
# File 'lib/heathrow/message_organizer.rb', line 23 def get_plugin_type(msg) return msg['source_type'] if msg['source_type'] @source_types_cache[msg['source_id']] || 'unknown' end |
#is_email_message?(msg) ⇒ Boolean
Detect if message is an email based on metadata
29 30 31 32 |
# File 'lib/heathrow/message_organizer.rb', line 29 def (msg) = (msg['metadata']) && ['message_id'] # Emails have Message-ID header end |
#organize_messages ⇒ Object
Organize messages into logical structures
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/heathrow/message_organizer.rb', line 35 def original_count = @messages.size filtered_count = 0 # Sort messages by timestamp ascending so root messages are processed before replies = @messages.sort_by { |m| m['timestamp'] || 0 } if ENV['DEBUG'] File.write('/tmp/heathrow_debug.log', "ORGANIZER: Sorted #{.size} messages by timestamp\n", mode: 'a') File.write('/tmp/heathrow_debug.log', "ORGANIZER: First 10 IDs in order: #{.first(10).map { |m| m['id'] }.join(', ')}\n", mode: 'a') end .each do |msg| # Skip synthetic header messages from previous organization if msg['is_header'] || msg['is_channel_header'] || msg['is_thread_header'] || msg['is_dm_header'] File.write('/tmp/heathrow_debug.log', "ORGANIZER: Skipping header message: #{msg['id']}\n", mode: 'a') if ENV['DEBUG'] next end filtered_count += 1 # If grouping by folder, use folder organization for all messages if @group_by_folder organize_by_folder(msg) next end # Get plugin type for this message plugin_type = get_plugin_type(msg) # Also check if it looks like an email based on metadata if plugin_type == 'unknown' && (msg) plugin_type = 'email' end # Stamp source_type on message so formatting code can use it msg['source_type'] = plugin_type case plugin_type when 'discord' (msg) when 'slack' (msg) when 'reddit' (msg) when 'telegram' (msg) when 'gmail', 'imap', 'email', 'maildir' organize_email_thread(msg) when 'rss' (msg) when 'web' (msg) when 'messenger' (msg) when 'instagram' (msg) when 'weechat' (msg) when 'workspace' (msg) else # Other/unknown sources - treat as simple messages in a channel (msg) end end # Build thread relationships build_thread_hierarchy File.write('/tmp/heathrow_debug.log', "ORGANIZER: Processed #{filtered_count}/#{original_count} messages (#{original_count - filtered_count} headers skipped)\n", mode: 'a') if ENV['DEBUG'] end |