summaryrefslogtreecommitdiff
path: root/app/models/integrations/chat_message/base_message.rb
blob: ab213f4b43ff974b25359e9cad2fe80da9fdb2a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
106
# frozen_string_literal: true

module Integrations
  module ChatMessage
    class BaseMessage
      RELATIVE_LINK_REGEX = %r{!\[[^\]]*\]\((/uploads/[^\)]*)\)}.freeze

      # Markup characters which are used for links in HTML, Markdown,
      # and Slack "mrkdwn" syntax (`<http://example.com|Label>`).
      UNSAFE_MARKUP_CHARACTERS = '<>[]|'

      attr_reader :markdown
      attr_reader :user_full_name
      attr_reader :user_name
      attr_reader :user_avatar
      attr_reader :project_name
      attr_reader :project_url

      def initialize(params)
        @markdown = params[:markdown] || false
        @project_name = params[:project_name] || params.dig(:project, :path_with_namespace)
        @project_url = params.dig(:project, :web_url) || params[:project_url]
        @user_full_name = params.dig(:user, :name) || params[:user_full_name]
        @user_name = params.dig(:user, :username) || params[:user_name]
        @user_avatar = params.dig(:user, :avatar_url) || params[:user_avatar]
      end

      def user_combined_name
        if user_full_name.present?
          "#{user_full_name} (#{user_name})"
        else
          user_name
        end
      end

      def summary
        return message if markdown

        format(message)
      end

      def pretext
        summary
      end

      def fallback
        format(message)
      end

      def attachments
        raise NotImplementedError
      end

      def activity
        raise NotImplementedError
      end

      private

      def message
        raise NotImplementedError
      end

      def format(string)
        ::Slack::Messenger::Util::LinkFormatter.format(format_relative_links(string))
      end

      def format_relative_links(string)
        string.gsub(RELATIVE_LINK_REGEX, "#{project_url}\\1")
      end

      # Remove unsafe markup from user input, which can be used to hijack links in our own markup,
      # or insert new ones.
      #
      # This currently removes Markdown and Slack "mrkdwn" links (keeping the link label),
      # and all HTML markup (keeping the text nodes).
      # We can't just escape the markup characters, because each chat app handles this differently.
      #
      # See:
      # - https://api.slack.com/reference/surfaces/formatting#escaping
      # - https://gitlab.com/gitlab-org/slack-notifier#escaping
      def strip_markup(string)
        string&.delete(UNSAFE_MARKUP_CHARACTERS)
      end

      def attachment_color
        '#345'
      end

      def link(text, url)
        "[#{strip_markup(text)}](#{url})"
      end

      def pretty_duration(seconds)
        parse_string =
          if duration < 1.hour
            '%M:%S'
          else
            '%H:%M:%S'
          end

        Time.at(seconds).utc.strftime(parse_string)
      end
    end
  end
end