Shad3yz

Untitled

May 29th, 2022 (edited)
213
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 6.84 KB | None | 0 0
  1. # typed: ignore
  2. # frozen_string_literal: true
  3.  
  4. module TextFormatting
  5.   module Inbound
  6.     module Formatters
  7.       class Telegram < Formatter
  8.      
  9.         # Example of payload with formatting
  10.         #
  11.         # {
  12.         #   text: "Normal Bold _Italic_ Strikethrough _Bold and Italic_  ~~Bold and Strikethrough~~ _~~Italic and Strikethrough~~_",
  13.         #   entities: [
  14.         #       {
  15.         #           offset: 7,
  16.         #           length: 4,
  17.         #           type: "bold"
  18.         #       },
  19.         #       {
  20.         #           offset: 21,
  21.         #           length: 13,
  22.         #           type: "strikethrough"
  23.         #       },
  24.         #       {
  25.         #           offset: 35,
  26.         #           length: 17,
  27.         #           type: "bold"
  28.         #       },
  29.         #       {
  30.         #           offset: 54,
  31.         #           length: 26,
  32.         #           type: "bold"
  33.         #       }
  34.         #   ]
  35.         # }
  36.  
  37.         sig { override.returns(::String) }
  38.         def format
  39.           return original_text if formatting_entities.blank?
  40.  
  41.           # escape original text from GFM formatting characters like: * _ ~~
  42.           formatted_text = escape_text_and_update_entities(original_text)
  43.           remove_trailing_whitespace_from_formatting(formatted_text)
  44.          
  45.           # pass by each formatting entity, and apply the corresponding formatting
  46.           # according to the offset and length range
  47.           formatting_entities.each do |e|
  48.             offset = e.dig(:offset)
  49.             length = e.dig(:length)
  50.             type = e.dig(:type)
  51.  
  52.             # get the corresponding formatting entity string
  53.             f_entity = evaluate_formatting_entity(type)
  54.             next if f_entity.nil?
  55.  
  56.             # capture the range to be transformed
  57.             transformation_range = offset .. offset + length - 1
  58.             formatted_text[transformation_range] = "#{f_entity}#{formatted_text[transformation_range]}#{f_entity}"
  59.  
  60.             # since we added characters to the string, we will need to shift all preceding formatting entities
  61.             # by the amount of characters we added accordingly
  62.             # this step adjusts the offset values according to the formatting applied on the range
  63.             shift_preceding_entities(offset, length, f_entity)
  64.           end
  65.  
  66.           formatted_text
  67.         end
  68.      
  69.         private
  70.  
  71.         def escape_text_and_update_entities(original_text)
  72.           # get the indices of the characters to be escaped
  73.           # we will need those to shift entities affected
  74.           matched_entities_indices = original_text.gsub(/\*|_|(~~)/).map { Regexp.last_match.begin(0) }
  75.          
  76.           # for each formatting entity, we need to check the indices generated by adding escape characters
  77.           # there are two cases:
  78.           #  1. the index is before the offset of the entity
  79.           #     in that case, we need to shift the entity's offset by 1
  80.           #     example:  normal * italic text
  81.           #                        _         _
  82.           #     the * symbol will be escaped ("*" -> "\*")
  83.           #     therefore the offset value of the bold formatting should shift by 1
  84.           #
  85.           #  2. the index is between the offset and the length
  86.           #     in that case, we need to increase the entity's length by 1
  87.           #     example: normal italic * text
  88.           #                     _           _
  89.           #     the * symbol will be escaped ("*" -> "\*")
  90.           #     therefore the length of the bold formatting should increase by 1
  91.           formatting_entities.map! do |e|
  92.             shift = 0
  93.  
  94.             matched_entities_indices.each do |idx|
  95.               if e[:offset] > idx + shift
  96.                 e[:offset] += 1
  97.                 shift += 1
  98.               end
  99.  
  100.               e[:length] += 1 if e[:offset] <= idx + shift && e[:offset] + e[:length] > idx + shift
  101.             end
  102.  
  103.             e
  104.           end
  105.  
  106.           escape_text(original_text.dup)
  107.         end
  108.  
  109.         def remove_trailing_whitespace_from_formatting(formatted_text)
  110.           formatting_entities.map! do |e|
  111.             e_length = e.dig(:length)
  112.             e_offset = e.dig(:offset)
  113.            
  114.             # et the index of the trailing space in the substring to be formatted
  115.             transformation_range = e_offset .. e_offset + e_length - 1
  116.             trailing_whitespace_idx = formatted_text[transformation_range].index(/\s+$/)
  117.             preceding_whitespace_idx = formatted_text[transformation_range].index(/^(\s*)/)
  118.  
  119.             # calculate the difference in length
  120.             trailing_difference = trailing_whitespace_idx.nil? ? 0 : ((e_length) - trailing_whitespace_idx)
  121.            
  122.             # calculate the difference in offset
  123.             preceding_difference = preceding_whitespace_idx
  124.  
  125.             # subtract the length difference from the length of the entity
  126.             # to prevent it from adding whitespace before the entity
  127.             e[:length] -= (trailing_difference + preceding_difference)
  128.             e[:offset] += preceding_difference
  129.  
  130.             e
  131.           end
  132.         end
  133.  
  134.         def shift_preceding_entities(offset, length, f_entity)
  135.           # for each formatting entity, we need to pass by all preceding entities
  136.           # and increase their offset by the length of the characters added for formatting
  137.           # example: bold italic
  138.           #          *  * _    _
  139.           #          after parsing bold, the string will be
  140.           #          *bold* italic
  141.           #               _    _
  142.           #           the italic offset is broken, so we need to increase it by 2, the length of the formatting entity added
  143.          
  144.           formatting_entities.map! do |e|
  145.             e_offset = e.dig(:offset)
  146.            
  147.             # is the formatting entity partially or completely inside the current entity?
  148.             if e_offset >= offset && e_offset < offset + length
  149.               e[:offset] += f_entity.length
  150.             # is the formatting entity completely after the current entity?
  151.             elsif e_offset >= offset + length
  152.               e[:offset] += 2 * f_entity.length
  153.             end
  154.  
  155.             e
  156.           end
  157.         end
  158.  
  159.         def evaluate_formatting_entity(type)
  160.           return "**" if type == "bold"
  161.           return "_"  if type == "italic"
  162.           return "~~" if type == "strikethrough"
  163.            
  164.           nil
  165.         end
  166.  
  167.         def message
  168.           @message ||= source_data.dig(:message)
  169.         end
  170.  
  171.         def original_text
  172.           @original_text ||= message.dig(:text)
  173.         end
  174.  
  175.         def escape_text(text)
  176.           text.gsub('*', '\*').gsub('_', '\_').gsub('~', '\~')
  177.         end
  178.  
  179.  
  180.         def formatting_entities
  181.           @formatting_entities ||= message.dig(:entities)
  182.         end
  183.  
  184.       end
  185.     end
  186.   end
  187. end
Add Comment
Please, Sign In to add comment