Thursday, June 04, 2026 1:12:19 AM
> log_entry_component.rb
# frozen_string_literal: true

class LogEntryComponent < ApplicationComponent
  def on_load(entry:, search_text: nil)
    @entry = entry
    @search_text = search_text
  end

  def call
    content_tag(:span, class: "log-entry-content") do
      highlighted_content(parse_entry)
    end
  end

  private

  def strip_timestamps(entry)
    # Remove various timestamp formats at the beginning of entries
    entry
      .sub(/^\[([^\]]+)\]\s*\[([^\]]+)\]\s*/, "") # [timestamp] [thread] format
      .sub(/^\[([^\]]+)\]\s*/, "") # [timestamp] format
      .sub(/^\d{2}:\d{2}:\d{2}:\d+\s*/, "") # 02:55:06:071228 format
      .sub(/^\d{4}-\d{2}-\d{2} at \d{2}:\d{2}:\d{2} [AP]M UTC\s*/, "") # Full date timestamp
      .strip
  end

  def highlighted_content(content)
    return content if @search_text.blank?

    # Create a regex for case-insensitive matching
    search_regex = Regexp.new(Regexp.escape(@search_text), Regexp::IGNORECASE)

    # Replace matches with highlighted version - using different color from poptabs
    content.gsub(search_regex) do |match|
      content_tag(:mark, match, class: "bg-info text-white fw-bold px-1 rounded border")
    end.html_safe
  end

  def parse_entry
    # Strip timestamps first, then parse the clean content
    clean_entry = strip_timestamps(@entry)

    # Debug logging to see what's happening
    Rails.logger.debug "=== LOG ENTRY PARSING ==="
    Rails.logger.debug "Original: #{@entry.inspect}"
    Rails.logger.debug "Clean: #{clean_entry.inspect}"
    Rails.logger.debug "Territory operation? #{territory_operation?(clean_entry)}"
    Rails.logger.debug "Purchase/sale operation? #{purchase_sale_operation?(clean_entry)}"
    Rails.logger.debug "Death message? #{death_message?}"
    Rails.logger.debug "Timestamped entry? #{timestamped_entry?}"
    Rails.logger.debug "=========================="

    # Territory operations
    if territory_operation?(clean_entry)
      Rails.logger.debug "PARSING AS: Territory operation"
      return parse_territory_operation(clean_entry)
    end

    # Purchase/Sale operations
    if purchase_sale_operation?(clean_entry)
      Rails.logger.debug "PARSING AS: Purchase/sale operation"
      return parse_purchase_sale(clean_entry)
    end

    # Death messages - check this before timestamped since deaths have timestamps
    if death_message?
      Rails.logger.debug "PARSING AS: Death message"
      return parse_death_message
    end

    # Timestamped entries
    if timestamped_entry?
      Rails.logger.debug "PARSING AS: Timestamped entry"
      return parse_timestamped_entry
    end

    # Generic patterns
    Rails.logger.debug "PARSING AS: Generic entry"
    parse_generic_entry
  end

  def territory_operation?(entry = @entry)
    entry.match?(/PLAYER\s*\(\s*[\w]+\s*\).*?(STOLE A LEVEL|PAID.*RANSOM|PAID.*PROTECT TERRITORY|PURCHASE A TERRITORY FLAG|RESTORED THE FLAG|UPGRADE TERRITORY)/i)
  end

  def purchase_sale_operation?(entry = @entry)
    # More flexible regex to handle "REMOTE" transactions and weird usernames
    entry.match?(/PLAYER:\s*\(\s*[\w]+\s*\).*?(PURCHASED|SOLD)/i)
  end

  def death_message?
    # Check for the actual death message patterns, not just the word "died"
    @entry.match?(/(died because|died a|died an|died and|died due|died while|died\.|commited suicide|crashed to death|was killed|was team-killed)/i)
  end

  def timestamped_entry?
    # Handle more complex timestamp formats including brackets and thread info
    @entry.match?(/^\[?\d{2}:\d{2}:\d{2}/)
  end

  def parse_territory_operation(entry = @entry)
    case entry
    when /PLAYER\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+STOLE A LEVEL\s+(\d+)\s+FLAG FROM TERRITORY #(\d+)/i
      uid, player, level, territory_id = $1, $2, $3, $4
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("stole", :danger),
        " a ",
        content_tag(:span, "Level #{level}", class: "badge bg-info"),
        " flag from ",
        territory_badge(territory_id)
      ])

    when /PLAYER\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+PAID\s+([\d,]+)\s+POP TABS FOR THE RANSOM OF TERRITORY #(\d+)\s*\|\s*PLAYER TOTAL POP TABS:\s*([\d,]+)/i
      uid, player, amount, territory_id, total = $1, $2, $3, $4, $5
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("paid ransom", :warning),
        " of ",
        currency_badge(amount, "poptabs"),
        " for ",
        territory_badge(territory_id),
        " ",
        total_currency(total)
      ])

    when /PLAYER\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+PAID\s+([\d,]+)\s+POP TABS TO PROTECT TERRITORY #(\d+)\s*\|\s*PLAYER TOTAL POP TABS:\s*([\d,]+)/i
      uid, player, amount, territory_id, total = $1, $2, $3, $4, $5
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("paid protection", :success),
        " of ",
        currency_badge(amount, "poptabs"),
        " for ",
        territory_badge(territory_id),
        " ",
        total_currency(total)
      ])

    when /PLAYER\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+PAID\s+([\d,]+)\s+POP TABS TO PURCHASE A TERRITORY FLAG\s*\|\s*PLAYER TOTAL POP TABS:\s*([\d,]+)/i
      uid, player, amount, total = $1, $2, $3, $4
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("purchased", :primary),
        " territory flag for ",
        currency_badge(amount, "poptabs"),
        " ",
        total_currency(total)
      ])

    when /PLAYER\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+RESTORED THE FLAG OF TERRITORY #(\d+)/i
      uid, player, territory_id = $1, $2, $3
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("restored", :success),
        " the flag of ",
        territory_badge(territory_id)
      ])

    when /PLAYER\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+PAID\s+([\d,]+)\s+POP TABS TO UPGRADE TERRITORY #(\d+) TO LEVEL\s+(\d+)\s*\|\s*PLAYER TOTAL POP TABS:\s*([\d,]+)/i
      uid, player, amount, territory_id, level, total = $1, $2, $3, $4, $5, $6
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("upgraded", :info),
        " ",
        territory_badge(territory_id),
        " to ",
        content_tag(:span, "Level #{level}", class: "badge bg-info"),
        " for ",
        currency_badge(amount, "poptabs"),
        " ",
        total_currency(total)
      ])

    # Complex timestamped remote sales with cargo and balance (like line 523)
    when /\[([^\]]+)\]\s*\[([^\]]+)\]\s*PLAYER:\s*\(\s*([\w]+)\s*\)\s*R NSTR:\d+\s*\(([^)]+)\)\s*REMOTE SOLD ITEM:\s*(.+?)\s*\(ID#\s*(\d+)\)\s*with Cargo\s+(.+?)\s+FOR\s+([\d,.e\+]+)\s+POPTABS AND\s+([\d,.e\+]+)\s+RESPECT\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      timestamp, thread, uid, player, vehicle, vehicle_id, cargo, price, respect, total = $1, $2, $3, $4, $5, $6, $7, $8, $9, $10
      safe_join([
        content_tag(:span, "[#{timestamp}]", class: "text-muted me-2"),
        content_tag(:span, "[#{thread}]", class: "text-secondary me-2"),
        content_tag(:span, "[REMOTE]", class: "badge bg-secondary ms-1"),
        " ",
        player_badge(uid, player),
        " ",
        action_text("sold", :success),
        " ",
        vehicle_badge(vehicle),
        content_tag(:small, " ##{vehicle_id}", class: "text-muted"),
        content_tag(:small, " (#{cargo})", class: "text-info ms-1"),
        " for ",
        currency_badge(price, "poptabs"),
        " and ",
        respect_badge(respect),
        " ",
        total_currency(total)
      ])

    else
      content_tag(:span, entry, class: "text-muted")
    end
  end

  def parse_purchase_sale(entry = @entry)
    case entry
    # Remote purchase transactions (flexible username matching)
    when /PLAYER:\s*\(\s*([\w]+)\s*\)\s*.*?\s+REMOTE\s+PURCHASED ITEM\s+(.+?)\s+FOR\s+([\d,.e\+]+)\s+POPTABS\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      uid, item, price, total = $1, $2, $3, $4
      # Extract player name from the middle part if we can
      player_name = extract_player_name(entry, uid)
      safe_join([
        content_tag(:span, "[REMOTE]", class: "badge bg-secondary"),
        " ",
        player_badge(uid, player_name),
        " ",
        action_text("purchased", :primary),
        " ",
        item_badge(item),
        " for ",
        currency_badge(price, "poptabs"),
        " ",
        total_currency(total)
      ])

    when /PLAYER:\s*\(\s*([\w]+)\s*\)\s*.*?\s+REMOTE\s+PURCHASED VEHICLE\s+(.+?)\s+FOR\s+([\d,.e\+]+)\s+POPTABS\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      uid, vehicle, price, total = $1, $2, $3, $4
      player_name = extract_player_name(entry, uid)
      safe_join([
        content_tag(:span, "[REMOTE]", class: "badge bg-secondary"),
        " ",
        player_badge(uid, player_name),
        " ",
        action_text("purchased", :primary),
        " vehicle ",
        vehicle_badge(vehicle),
        " for ",
        currency_badge(price, "poptabs"),
        " ",
        total_currency(total)
      ])

    # Remote sale transactions
    when /PLAYER:\s*\(\s*([\w]+)\s*\)\s*.*?\s+REMOTE\s+SOLD ITEM\s+(.+?)\s+FOR\s+([\d,.e\+]+)\s+POPTABS AND\s+([\d,.e\+]+)\s+RESPECT\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      uid, item, price, respect, total = $1, $2, $3, $4, $5
      player_name = extract_player_name(entry, uid)
      safe_join([
        content_tag(:span, "[REMOTE]", class: "badge bg-secondary"),
        " ",
        player_badge(uid, player_name),
        " ",
        action_text("sold", :success),
        " ",
        item_badge(item),
        " for ",
        currency_badge(price, "poptabs"),
        " and ",
        respect_badge(respect),
        " ",
        total_currency(total)
      ])

    # Remote sale with cargo (the complex one)
    when /PLAYER:\s*\(\s*([\w]+)\s*\)\s*.*?\s+REMOTE\s+SOLD ITEM:\s*(.+?)\s*\(ID#\s*(\d+)\)\s*with Cargo\s+(.+?)\s+FOR\s+([\d,.e\+]+)\s+POPTABS AND\s+([\d,.e\+]+)\s+RESPECT\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      uid, vehicle, vehicle_id, cargo, price, respect, total = $1, $2, $3, $4, $5, $6, $7
      player_name = extract_player_name(entry, uid)
      safe_join([
        content_tag(:span, "[REMOTE]", class: "badge bg-secondary"),
        " ",
        player_badge(uid, player_name),
        " ",
        action_text("sold", :success),
        " ",
        vehicle_badge(vehicle),
        content_tag(:small, " ##{vehicle_id}", class: "text-muted"),
        content_tag(:small, " (#{cargo})", class: "text-info ms-1"),
        " for ",
        currency_badge(price, "poptabs"),
        " and ",
        respect_badge(respect),
        " ",
        total_currency(total)
      ])

    # Regular (non-remote) purchase transactions
    when /PLAYER:\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+PURCHASED ITEM\s+(.+?)\s+FOR\s+([\d,]+)\s+POPTABS\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      uid, player, item, price, total = $1, $2, $3, $4, $5
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("purchased", :primary),
        " ",
        item_badge(item),
        " for ",
        currency_badge(price, "poptabs"),
        " ",
        total_currency(total)
      ])

    when /PLAYER:\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+PURCHASED VEHICLE\s+(.+?)\s+FOR\s+([\d,]+)\s+POPTABS\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      uid, player, vehicle, price, total = $1, $2, $3, $4, $5
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("purchased", :primary),
        " vehicle ",
        vehicle_badge(vehicle),
        " for ",
        currency_badge(price, "poptabs"),
        " ",
        total_currency(total)
      ])

    when /PLAYER:\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+PURCHASED VEHICLE SKIN\s+(.+?)\s+\((.+?)\)\s+FOR\s+([\d,]+)\s+POPTABS\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      uid, player, skin, vehicle, price, total = $1, $2, $3, $4, $5, $6
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("purchased skin", :primary),
        " ",
        item_badge(skin),
        " for ",
        vehicle_badge(vehicle),
        " - ",
        currency_badge(price, "poptabs"),
        " ",
        total_currency(total)
      ])

    when /PLAYER:\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+SOLD ITEM\s+(.+?)\s+FOR\s+([\d,]+)\s+POPTABS AND\s+([\d,]+)\s+RESPECT\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      uid, player, item, price, respect, total = $1, $2, $3, $4, $5, $6
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("sold", :success),
        " ",
        item_badge(item),
        " for ",
        currency_badge(price, "poptabs"),
        " and ",
        respect_badge(respect),
        " ",
        total_currency(total)
      ])

    when /PLAYER:\s*\(\s*([\w]+)\s*\)\s*(.+?)\s+SOLD ITEM:\s*(.+?)\s*\(ID#\s*(\d+)\)\s*with Cargo\s+(.+?)\s+FOR\s+([\d,]+)\s+POPTABS AND\s+([\d,]+)\s+RESPECT\s*\|\s*PLAYER TOTAL MONEY:\s*([\d,]+)/i
      uid, player, vehicle, vehicle_id, cargo, price, respect, total = $1, $2, $3, $4, $5, $6, $7, $8
      safe_join([
        player_badge(uid, player),
        " ",
        action_text("sold", :success),
        " ",
        vehicle_badge(vehicle),
        content_tag(:small, " ##{vehicle_id}", class: "text-muted"),
        " for ",
        currency_badge(price, "poptabs"),
        " and ",
        respect_badge(respect),
        " ",
        total_currency(total)
      ])

    else
      content_tag(:span, @entry, class: "text-muted")
    end
  end

  def parse_death_message
    # First extract any timestamp if present
    if @entry.match?(/^\d{4}-\d{2}-\d{2} at \d{2}:\d{2}:\d{2} [AP]M UTC/i)
      timestamp_match = @entry.match(/^(\d{4}-\d{2}-\d{2} at \d{2}:\d{2}:\d{2} [AP]M UTC)\s*(.+)/i)
      if timestamp_match
        timestamp, message = timestamp_match[1], timestamp_match[2]
        return safe_join([
          content_tag(:span, timestamp, class: "text-info me-2"),
          parse_death_content(message)
        ])
      end
    end

    parse_death_content(@entry)
  end

  def parse_death_content(message)
    case message
    when /^(.+?)\s+died because.*Arma/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("died", :warning),
        content_tag(:small, " (Arma bug)", class: "text-muted")
      ])

    when /^(.+?)\s+died because.*universe hates/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("died", :warning),
        content_tag(:small, " (universe hates them)", class: "text-muted")
      ])

    when /^(.+?)\s+died because.*very unlucky/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("died", :warning),
        content_tag(:small, " (unlucky)", class: "text-muted")
      ])

    when /^(.+?)\s+died a mysterious death/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("died mysteriously", :warning)
      ])

    when /^(.+?)\s+died and nobody knows why/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("died", :warning),
        content_tag(:small, " (unknown reason)", class: "text-muted")
      ])

    when /^(.+?)\s+died because that's why/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("died", :warning),
        content_tag(:small, " (because)", class: "text-muted")
      ])

    when /^(.+?)\s+died due to Arma bugs/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("died", :warning),
        content_tag(:small, " (Arma bugs - probably salty)", class: "text-muted")
      ])

    when /^(.+?)\s+died an awkward death/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("died awkwardly", :warning)
      ])

    when /^(.+?)\s+died\.\s*Yes.*really dead/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("died", :warning),
        content_tag(:small, " (really dead-dead)", class: "text-muted")
      ])

    when /^(.+?)\s+comm?itted suicide/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("committed suicide", :warning)
      ])

    when /^(.+?)\s+died while playing Russian Roulette/i
      player = $1
      safe_join([
        player_name(player),
        " lost at ",
        action_text("Russian Roulette", :danger)
      ])

    when /^(.+?)\s+crashed to death/i
      player = $1
      safe_join([
        player_name(player),
        " ",
        action_text("crashed to death", :warning)
      ])

    when /^(.+?)\s+was killed by an NPC/i
      player = $1
      safe_join([
        player_name(player),
        " was killed by an ",
        action_text("NPC", :info)
      ])

    when /^(.+?)\s+was team-killed by\s+(.+?)[!.]/i
      victim, killer = $1, $2
      safe_join([
        player_name(victim),
        " was ",
        action_text("team-killed", :danger),
        " by ",
        player_name(killer)
      ])

    when /^(.+?)\s+was killed by\s+(.+?)!.*BAMBI SLAYER/i
      victim, killer = $1, $2
      safe_join([
        player_name(victim),
        " was killed by ",
        player_name(killer),
        " ",
        content_tag(:span, "[BAMBI SLAYER]", class: "badge bg-danger")
      ])

    when /^(.+?)\s+was killed by\s+(.+?)!\s*\(([^)]+)\)/i
      victim, killer, perks = $1, $2, $3
      perk_badges = perks.split(",").map { |perk| content_tag(:span, perk.strip, class: "badge bg-info ms-1") }
      safe_join([
        player_name(victim),
        " was killed by ",
        player_name(killer)
      ] + perk_badges)

    when /^(.+?)\s+was killed by\s+(.+?)[!.]/i
      victim, killer = $1, $2
      safe_join([
        player_name(victim),
        " was killed by ",
        player_name(killer)
      ])

    else
      content_tag(:span, message, class: "text-light")
    end
  end

  def parse_timestamped_entry
    # Handle complex timestamp formats like [02:55:06:071228 --5:00] [Thread 91468]
    if @entry.match?(/^\[([^\]]+)\]\s*\[([^\]]+)\]\s*(.+)/)
      timestamp, thread, message = $1, $2, $3
      safe_join([
        content_tag(:span, "[#{timestamp}]", class: "text-muted me-2"),
        content_tag(:span, "[#{thread}]", class: "text-secondary me-2"),
        content_tag(:span, message, class: "text-light")
      ])
    else
      # Simple timestamp format
      timestamp = @entry[0..7]
      message = @entry[9..]

      safe_join([
        content_tag(:span, timestamp, class: "text-muted me-2"),
        content_tag(:span, message, class: "text-light")
      ])
    end
  end

  def parse_generic_entry
    case @entry
    when /WARNING|Error|Failed/i
      content_tag(:span, @entry, class: "text-warning")
    when /SUCCESS|Loaded|Started|Connected/i
      content_tag(:span, @entry, class: "text-success")
    when /INFO|Loading|Initializing/i
      content_tag(:span, @entry, class: "text-info")
    else
      content_tag(:span, @entry, class: "text-light")
    end
  end

  # Helper methods for consistent formatting
  def player_badge(uid, name)
    safe_join([
      content_tag(:i, "", class: "bi bi-person-fill text-info me-1"),
      player_name(name),
      " ",
      content_tag(:small, "(#{uid})", class: "text-muted")
    ])
  end

  def player_name(name)
    content_tag(:span, name, class: "text-info fw-semibold")
  end

  def action_text(action, style)
    content_tag(:span, action, class: "text-#{style} fw-semibold")
  end

  def currency_badge(amount, type = "")
    # Convert scientific notation, then let Rails handle the formatting
    normalized = amount.to_s.match?(/e[+-]?\d+/i) ? amount.to_f.to_i : amount.to_i
    formatted_amount = helpers.number_with_delimiter(normalized)
    content_tag(:span, "#{formatted_amount} #{type}".strip, class: "badge bg-warning text-dark")
  end

  def respect_badge(amount)
    # Convert scientific notation, then let Rails handle the formatting
    normalized = amount.to_s.match?(/e[+-]?\d+/i) ? amount.to_f.to_i : amount.to_i
    formatted_amount = helpers.number_with_delimiter(normalized)
    content_tag(:span, "+#{formatted_amount} respect", class: "badge bg-success")
  end

  def total_currency(amount)
    # Convert scientific notation, then let Rails handle the formatting
    normalized = amount.to_s.match?(/e[+-]?\d+/i) ? amount.to_f.to_i : amount.to_i
    formatted_amount = helpers.number_with_delimiter(normalized)
    content_tag(:small, "(Balance: #{formatted_amount})", class: "text-muted")
  end

  def territory_badge(id)
    content_tag(:span, "Territory ##{id}", class: "badge bg-primary")
  end

  def item_badge(item)
    content_tag(:span, item, class: "badge bg-secondary")
  end

  def vehicle_badge(vehicle)
    content_tag(:span, vehicle, class: "badge bg-info text-dark")
  end

  def extract_player_name(entry, uid)
    # Try to extract player name from the middle part between UID and REMOTE
    # Format: "PLAYER: ( UID ) some_stuff (actual_name) REMOTE ..."
    middle_match = entry.match(/PLAYER:\s*\(\s*#{Regexp.escape(uid)}\s*\)\s*(.+?)\s+REMOTE/i)
    return "Unknown" unless middle_match

    middle_part = middle_match[1]

    # Look for name in parentheses (most common case)
    if (name_match = middle_part.match(/\(([^)]+)\)/))
      name_match[1]
    else
      # Fallback: take the last word before REMOTE
      middle_part.split.last || "Player"
    end
  end
end
All opinions represented herein are my own
- © 2024 - 2026 itsthedevman
- build 4294fb2