slack-notifier 1.5.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/slack-notifier.rb +35 -60
- data/lib/slack-notifier/config.rb +37 -0
- data/lib/slack-notifier/payload_middleware.rb +21 -0
- data/lib/slack-notifier/payload_middleware/base.rb +35 -0
- data/lib/slack-notifier/payload_middleware/format_attachments.rb +37 -0
- data/lib/slack-notifier/payload_middleware/format_message.rb +19 -0
- data/lib/slack-notifier/payload_middleware/stack.rb +35 -0
- data/lib/slack-notifier/util/escape.rb +15 -0
- data/lib/slack-notifier/util/http_client.rb +53 -0
- data/lib/slack-notifier/util/link_formatter.rb +63 -0
- data/lib/slack-notifier/version.rb +2 -1
- data/spec/end_to_end_spec.rb +84 -0
- data/spec/integration/ping_integration_test.rb +12 -5
- data/spec/lib/slack-notifier/config_spec.rb +71 -0
- data/spec/lib/slack-notifier/payload_middleware/base_spec.rb +75 -0
- data/spec/lib/slack-notifier/payload_middleware/format_attachments_spec.rb +35 -0
- data/spec/lib/slack-notifier/payload_middleware/format_message_spec.rb +26 -0
- data/spec/lib/slack-notifier/payload_middleware/stack_spec.rb +93 -0
- data/spec/lib/slack-notifier/payload_middleware_spec.rb +32 -0
- data/spec/lib/slack-notifier/util/http_client_spec.rb +34 -0
- data/spec/lib/slack-notifier/{link_formatter_spec.rb → util/link_formatter_spec.rb} +30 -19
- data/spec/lib/slack-notifier_spec.rb +62 -128
- data/spec/spec_helper.rb +20 -5
- metadata +30 -9
- data/lib/slack-notifier/default_http_client.rb +0 -51
- data/lib/slack-notifier/link_formatter.rb +0 -62
- data/spec/lib/slack-notifier/default_http_client_spec.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47af99d5a1eeca46a62425be8842bc6ff89d9d51
|
4
|
+
data.tar.gz: 3d0519d10cb7a251a49b32b0d27b2e0616f08c54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d9a915eb0ebc25b3d1d4042055419cf8628404cf9282fffa41a4f4dfada1eb8aac4c02c9b7415226895a160ee326f0c82d048fa84ede13f829dac19130b2cc8
|
7
|
+
data.tar.gz: 314a2d7812c2d699f201753e15fbcd6c34c778f863123f2e70e7731d8753a338e984bbcb3dd4cd125f01509990b79fc84ac8a3b7daf5b2e70f5484c66cede356
|
data/lib/slack-notifier.rb
CHANGED
@@ -1,81 +1,56 @@
|
|
1
|
-
|
2
|
-
require
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "uri"
|
3
|
+
require "json"
|
4
4
|
|
5
|
-
require_relative
|
6
|
-
require_relative
|
5
|
+
require_relative "slack-notifier/util/http_client"
|
6
|
+
require_relative "slack-notifier/util/link_formatter"
|
7
|
+
require_relative "slack-notifier/util/escape"
|
8
|
+
require_relative "slack-notifier/payload_middleware"
|
9
|
+
require_relative "slack-notifier/config"
|
7
10
|
|
8
11
|
module Slack
|
9
12
|
class Notifier
|
10
|
-
attr_reader :endpoint
|
13
|
+
attr_reader :endpoint
|
11
14
|
|
12
|
-
def initialize webhook_url, options={}
|
13
|
-
@endpoint
|
14
|
-
@default_payload = options
|
15
|
-
end
|
16
|
-
|
17
|
-
def ping message, options={}
|
18
|
-
if message.is_a?(Hash)
|
19
|
-
message, options = nil, message
|
20
|
-
end
|
21
|
-
|
22
|
-
if attachments = options[:attachments] || options["attachments"]
|
23
|
-
wrap_array(attachments).each do |attachment|
|
24
|
-
["text", :text].each do |key|
|
25
|
-
attachment[key] = LinkFormatter.format(attachment[key]) if attachment.has_key?(key)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
payload = default_payload.merge(options)
|
31
|
-
client = payload.delete(:http_client) || http_client
|
32
|
-
http_options = payload.delete(:http_options)
|
33
|
-
|
34
|
-
unless message.nil?
|
35
|
-
payload.merge!(text: LinkFormatter.format(message))
|
36
|
-
end
|
15
|
+
def initialize webhook_url, options={}, &block
|
16
|
+
@endpoint = URI.parse webhook_url
|
37
17
|
|
38
|
-
|
39
|
-
|
18
|
+
config.http_client(options.delete(:http_client)) if options.key?(:http_client)
|
19
|
+
config.defaults options
|
20
|
+
config.instance_exec(&block) if block_given?
|
40
21
|
|
41
|
-
|
22
|
+
middleware.set config.middleware
|
42
23
|
end
|
43
24
|
|
44
|
-
def
|
45
|
-
|
25
|
+
def config
|
26
|
+
@_config ||= Config.new
|
46
27
|
end
|
47
28
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
29
|
+
def ping message, options={}
|
30
|
+
if message.is_a?(Hash)
|
31
|
+
options = message
|
32
|
+
else
|
33
|
+
options[:text] = message
|
34
|
+
end
|
51
35
|
|
52
|
-
|
53
|
-
default_payload[:channel] = channel
|
36
|
+
post options
|
54
37
|
end
|
55
38
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
39
|
+
def post payload={}
|
40
|
+
params = {}
|
41
|
+
client = payload.delete(:http_client) || config.http_client
|
42
|
+
payload = config.defaults.merge(payload)
|
59
43
|
|
60
|
-
|
61
|
-
|
62
|
-
end
|
44
|
+
params[:http_options] = payload.delete(:http_options) if payload.key?(:http_options)
|
45
|
+
params[:payload] = middleware.call(payload).to_json
|
63
46
|
|
64
|
-
|
65
|
-
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<' }
|
66
|
-
|
67
|
-
def escape(text)
|
68
|
-
text.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
|
47
|
+
client.post endpoint, params
|
69
48
|
end
|
70
49
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
object.to_ary || [object]
|
76
|
-
else
|
77
|
-
[object]
|
50
|
+
private
|
51
|
+
|
52
|
+
def middleware
|
53
|
+
@middleware ||= PayloadMiddleware::Stack.new(self)
|
78
54
|
end
|
79
|
-
end
|
80
55
|
end
|
81
56
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Slack
|
3
|
+
class Notifier
|
4
|
+
class Config
|
5
|
+
def initialize
|
6
|
+
@http_client = Util::HTTPClient
|
7
|
+
@defaults = {}
|
8
|
+
@middleware = [:format_message, :format_attachments]
|
9
|
+
end
|
10
|
+
|
11
|
+
def http_client client=nil
|
12
|
+
return @http_client if client.nil?
|
13
|
+
raise ArgumentError, "the http client must respond to ::post" unless client.respond_to?(:post)
|
14
|
+
|
15
|
+
@http_client = client
|
16
|
+
end
|
17
|
+
|
18
|
+
def defaults new_defaults=nil
|
19
|
+
return @defaults if new_defaults.nil?
|
20
|
+
raise ArgumentError, "the defaults must be a Hash" unless new_defaults.is_a?(Hash)
|
21
|
+
|
22
|
+
@defaults = new_defaults
|
23
|
+
end
|
24
|
+
|
25
|
+
def middleware *args
|
26
|
+
return @middleware if args.empty?
|
27
|
+
|
28
|
+
@middleware =
|
29
|
+
if args.length == 1 && args.first.is_a?(Array) || args.first.is_a?(Hash)
|
30
|
+
args.first
|
31
|
+
else
|
32
|
+
args
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Slack
|
3
|
+
class Notifier
|
4
|
+
class PayloadMiddleware
|
5
|
+
class << self
|
6
|
+
def registry
|
7
|
+
@registry ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def register middleware, name
|
11
|
+
registry[name] = middleware
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require_relative "payload_middleware/stack"
|
19
|
+
require_relative "payload_middleware/base"
|
20
|
+
require_relative "payload_middleware/format_message"
|
21
|
+
require_relative "payload_middleware/format_attachments"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Slack
|
3
|
+
class Notifier
|
4
|
+
class PayloadMiddleware
|
5
|
+
class Base
|
6
|
+
class << self
|
7
|
+
def middleware_name name
|
8
|
+
PayloadMiddleware.register self, name.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def options default_opts
|
12
|
+
@default_opts = default_opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_opts
|
16
|
+
@default_opts ||= {}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :notifier, :options
|
21
|
+
|
22
|
+
def initialize notifier, opts={}
|
23
|
+
@notifier = notifier
|
24
|
+
@options = self.class.default_opts.merge opts
|
25
|
+
end
|
26
|
+
|
27
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
28
|
+
def call payload={}
|
29
|
+
raise NoMethodError, "method `call` not defined for class #{self.class}"
|
30
|
+
end
|
31
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Slack
|
3
|
+
class Notifier
|
4
|
+
class PayloadMiddleware
|
5
|
+
class FormatAttachments < Base
|
6
|
+
middleware_name :format_attachments
|
7
|
+
|
8
|
+
options formats: [:html, :markdown]
|
9
|
+
|
10
|
+
def call payload={}
|
11
|
+
attachments = payload.fetch(:attachments, payload["attachments"])
|
12
|
+
wrap_array(attachments).each do |attachment|
|
13
|
+
["text", :text].each do |key|
|
14
|
+
if attachment.key?(key)
|
15
|
+
attachment[key] = Util::LinkFormatter.format(attachment[key], options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
payload
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def wrap_array object
|
26
|
+
if object.nil?
|
27
|
+
[]
|
28
|
+
elsif object.respond_to?(:to_ary)
|
29
|
+
object.to_ary || [object]
|
30
|
+
else
|
31
|
+
[object]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Slack
|
3
|
+
class Notifier
|
4
|
+
class PayloadMiddleware
|
5
|
+
class FormatMessage < Base
|
6
|
+
middleware_name :format_message
|
7
|
+
|
8
|
+
options formats: [:html, :markdown]
|
9
|
+
|
10
|
+
def call payload={}
|
11
|
+
return payload unless payload[:text]
|
12
|
+
payload[:text] = Util::LinkFormatter.format(payload[:text], options)
|
13
|
+
|
14
|
+
payload
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Slack
|
3
|
+
class Notifier
|
4
|
+
class PayloadMiddleware
|
5
|
+
class Stack
|
6
|
+
attr_reader :notifier,
|
7
|
+
:stack
|
8
|
+
|
9
|
+
def initialize notifier
|
10
|
+
@notifier = notifier
|
11
|
+
@stack = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def set *middlewares
|
15
|
+
middlewares =
|
16
|
+
if middlewares.length == 1 && middlewares.first.is_a?(Hash)
|
17
|
+
middlewares.first
|
18
|
+
else
|
19
|
+
middlewares.flatten
|
20
|
+
end
|
21
|
+
|
22
|
+
@stack = middlewares.map do |key, opts|
|
23
|
+
PayloadMiddleware.registry.fetch(key).new(*[notifier, opts].compact)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def call payload={}
|
28
|
+
stack.inject payload do |pld, middleware|
|
29
|
+
middleware.call(pld)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Slack
|
3
|
+
class Notifier
|
4
|
+
module Util
|
5
|
+
module Escape
|
6
|
+
HTML_REGEXP = /[&><]/
|
7
|
+
HTML_REPLACE = { "&" => "&", ">" => ">", "<" => "<" }.freeze
|
8
|
+
|
9
|
+
def self.html string
|
10
|
+
string.gsub(HTML_REGEXP, HTML_REPLACE)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
|
5
|
+
module Slack
|
6
|
+
class Notifier
|
7
|
+
module Util
|
8
|
+
class HTTPClient
|
9
|
+
class << self
|
10
|
+
def post uri, params
|
11
|
+
HTTPClient.new(uri, params).call
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :uri, :params, :http_options
|
16
|
+
|
17
|
+
def initialize uri, params
|
18
|
+
@uri = uri
|
19
|
+
@http_options = params.delete(:http_options) || {}
|
20
|
+
@params = params
|
21
|
+
end
|
22
|
+
|
23
|
+
def call
|
24
|
+
http_obj.request request_obj
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def request_obj
|
30
|
+
req = Net::HTTP::Post.new uri.request_uri
|
31
|
+
req.set_form_data params
|
32
|
+
|
33
|
+
req
|
34
|
+
end
|
35
|
+
|
36
|
+
def http_obj
|
37
|
+
http = Net::HTTP.new uri.host, uri.port
|
38
|
+
http.use_ssl = (uri.scheme == "https")
|
39
|
+
|
40
|
+
http_options.each do |opt, val|
|
41
|
+
if http.respond_to? "#{opt}="
|
42
|
+
http.send "#{opt}=", val
|
43
|
+
else
|
44
|
+
warn "Net::HTTP doesn't respond to `#{opt}=`, ignoring that option"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
http
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Slack
|
3
|
+
class Notifier
|
4
|
+
module Util
|
5
|
+
class LinkFormatter
|
6
|
+
# http://rubular.com/r/19cNXW5qbH
|
7
|
+
HTML_PATTERN = / <a (?:.*?) href=['"](.+?)['"] (?:.*?)> (.+?) <\/a> /x
|
8
|
+
|
9
|
+
# http://rubular.com/r/guJbTK6x1f
|
10
|
+
MARKDOWN_PATTERN = /\[ ([^\[\]]*?) \] \( ((https?:\/\/.*?) | (mailto:.*?)) \) /x
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def format string, opts={}
|
14
|
+
LinkFormatter.new(string, opts).formatted
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :formats
|
19
|
+
|
20
|
+
def initialize string, formats: [:html, :markdown]
|
21
|
+
@formats = formats
|
22
|
+
@orig = string.respond_to?(:scrub) ? string.scrub : string
|
23
|
+
end
|
24
|
+
|
25
|
+
# rubocop:disable Style/GuardClause
|
26
|
+
def formatted
|
27
|
+
sub_markdown_links(sub_html_links(@orig))
|
28
|
+
rescue => e
|
29
|
+
if RUBY_VERSION < "2.1" && e.message.include?("invalid byte sequence")
|
30
|
+
raise e, "#{e.message}. Consider including the 'string-scrub' gem to strip invalid characters"
|
31
|
+
else
|
32
|
+
raise e
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# rubocop:enable Style/GuardClause
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def sub_html_links string
|
40
|
+
return string unless formats.include?(:html)
|
41
|
+
|
42
|
+
string.gsub(HTML_PATTERN) do
|
43
|
+
slack_link Regexp.last_match[1], Regexp.last_match[2]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def sub_markdown_links string
|
48
|
+
return string unless formats.include?(:markdown)
|
49
|
+
|
50
|
+
string.gsub(MARKDOWN_PATTERN) do
|
51
|
+
slack_link Regexp.last_match[2], Regexp.last_match[1]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def slack_link link, text=nil
|
56
|
+
"<#{link}" \
|
57
|
+
"#{text && !text.empty? ? "|#{text}" : ''}" \
|
58
|
+
">"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|