File: discord.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (131 lines) | stat: -rw-r--r-- 3,850 bytes parent folder | download
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# frozen_string_literal: true

require "discordrb/webhooks"

module Integrations
  class Discord < BaseChatNotification
    ATTACHMENT_REGEX = Gitlab::UntrustedRegexp.new(': (?<entry>[^\n]*)\n - (?<name>[^\n]*)\n*')

    field :webhook,
      section: SECTION_TYPE_CONNECTION,
      description: -> { _('Discord webhook (for example, `https://discord.com/api/webhooks/…`).') },
      help: 'e.g. https://discord.com/api/webhooks/…',
      required: true

    field :notify_only_broken_pipelines,
      type: :checkbox,
      section: SECTION_TYPE_CONFIGURATION,
      description: -> { _('Send notifications for broken pipelines.') }

    field :branches_to_be_notified,
      type: :select,
      section: SECTION_TYPE_CONFIGURATION,
      title: -> { s_('Integrations|Branches for which notifications are to be sent') },
      description: -> { _('Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`.') },
      choices: -> { branch_choices }

    def self.title
      s_("DiscordService|Discord Notifications")
    end

    def self.description
      s_("DiscordService|Send notifications about project events to a Discord channel.")
    end

    def self.help
      build_help_page_url(
        'user/project/integrations/discord_notifications.md',
        s_("DiscordService|Send notifications about project events to a Discord channel."),
        _('How do I set up this integration?')
      )
    end

    def self.to_param
      "discord"
    end

    def default_channel_placeholder
      s_('DiscordService|Override the default webhook (e.g. https://discord.com/api/webhooks/…)')
    end

    override :supported_events
    def supported_events
      additional = group_level? ? %w[group_mention group_confidential_mention] : []

      (self.class.supported_events + additional).freeze
    end

    def self.supported_events
      %w[push issue confidential_issue merge_request note confidential_note tag_push pipeline wiki_page deployment]
    end

    def configurable_channels?
      true
    end

    def channel_limit_per_event
      1
    end

    def mask_configurable_channels?
      true
    end

    private

    def notify(message, opts)
      webhook_url = opts[:channel]&.first || webhook
      client = Discordrb::Webhooks::Client.new(url: webhook_url)

      client.execute do |builder|
        builder.add_embed do |embed|
          embed.author = Discordrb::Webhooks::EmbedAuthor.new(name: message.user_name, icon_url: message.user_avatar)
          embed.description = (message.pretext + "\n" + Array.wrap(message.attachments).join("\n"))

          if ATTACHMENT_REGEX.match?(embed.description)
            embed.description = ATTACHMENT_REGEX.replace_gsub(embed.description) do |match|
              " #{match[:entry]} - #{match[:name]}\n"
            end
          end

          embed.colour = embed_color(message)
          embed.timestamp = Time.now.utc
        end
      end
    rescue RestClient::Exception => e
      log_error(e.message)
      false
    end

    COLOR_OVERRIDES = {
      'good' => '#0d532a',
      'warning' => '#703800',
      'danger' => '#8d1300'
    }.freeze

    def embed_color(message)
      return 'fc6d26'.hex unless message.respond_to?(:attachment_color)

      color = message.attachment_color

      color = COLOR_OVERRIDES[color] if COLOR_OVERRIDES.key?(color)

      color = color.delete_prefix('#')

      normalize_color(color).hex
    end

    # Expands the short notation to the full colorcode notation
    # 123456 -> 123456
    # 123    -> 112233
    def normalize_color(color)
      return (color[0, 1] * 2) + (color[1, 1] * 2) + (color[2, 1] * 2) if color.length == 3

      color
    end

    def custom_data(data)
      super(data).merge(markdown: true)
    end
  end
end