slack-messenger-2.3.5/ 0000775 0001750 0001750 00000000000 14715406113 013634 5 ustar pravi pravi slack-messenger-2.3.5/lib/ 0000775 0001750 0001750 00000000000 14715406113 014402 5 ustar pravi pravi slack-messenger-2.3.5/lib/slack-messenger.rb 0000644 0001750 0001750 00000002574 14715406113 020020 0 ustar pravi pravi # frozen_string_literal: true
require "uri"
require "json"
require_relative "slack-messenger/util/http_client"
require_relative "slack-messenger/util/link_formatter"
require_relative "slack-messenger/util/escape"
require_relative "slack-messenger/payload_middleware"
require_relative "slack-messenger/config"
module Slack
class Messenger
attr_reader :endpoint
def initialize webhook_url, options={}, &block
@endpoint = URI.parse webhook_url
config.http_client(options.delete(:http_client)) if options.key?(:http_client)
config.defaults options
config.instance_exec(&block) if block_given?
middleware.set config.middleware
end
def config
@_config ||= Config.new
end
def ping message, options={}
if message.is_a?(Hash)
options = message
else
options[:text] = message
end
post options
end
def post payload={}
params = {}
client = payload.delete(:http_client) || config.http_client
payload = config.defaults.merge(payload)
params[:http_options] = payload.delete(:http_options) if payload.key?(:http_options)
middleware.call(payload).map do |pld|
params[:payload] = pld.to_json
client.post endpoint, params
end
end
private
def middleware
@middleware ||= PayloadMiddleware::Stack.new(self)
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/ 0000775 0001750 0001750 00000000000 14715406113 017465 5 ustar pravi pravi slack-messenger-2.3.5/lib/slack-messenger/config.rb 0000644 0001750 0001750 00000001763 14715406113 021264 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
class Config
def initialize
@http_client = Util::HTTPClient
@defaults = {}
@middleware = %i[
format_message
format_attachments
at
channels
]
end
def http_client client=nil
return @http_client if client.nil?
raise ArgumentError, "the http client must respond to ::post" unless client.respond_to?(:post)
@http_client = client
end
def defaults new_defaults=nil
return @defaults if new_defaults.nil?
raise ArgumentError, "the defaults must be a Hash" unless new_defaults.is_a?(Hash)
@defaults = new_defaults
end
def middleware *args
return @middleware if args.empty?
@middleware =
if args.length == 1 && args.first.is_a?(Array) || args.first.is_a?(Hash)
args.first
else
args
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/payload_middleware.rb 0000644 0001750 0001750 00000001075 14715406113 023641 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
class PayloadMiddleware
class << self
def registry
@registry ||= {}
end
def register middleware, name
registry[name] = middleware
end
end
end
end
end
require_relative "payload_middleware/stack"
require_relative "payload_middleware/base"
require_relative "payload_middleware/format_message"
require_relative "payload_middleware/format_attachments"
require_relative "payload_middleware/at"
require_relative "payload_middleware/channels"
slack-messenger-2.3.5/lib/slack-messenger/payload_middleware/ 0000775 0001750 0001750 00000000000 14715406113 023313 5 ustar pravi pravi slack-messenger-2.3.5/lib/slack-messenger/payload_middleware/at.rb 0000644 0001750 0001750 00000001321 14715406113 024237 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
class PayloadMiddleware
class At < Base
middleware_name :at
options at: []
def call payload={}
return payload unless payload[:at]
payload[:text] = "#{format_ats(payload.delete(:at))}#{payload[:text]}"
payload
end
private
def format_ats ats
Array(ats).map { |at| "<#{at_cmd_char(at)}#{at}> " }
.join("")
end
def at_cmd_char at
case at
when :here, :channel, :everyone, :group
"!"
else
"@"
end
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/payload_middleware/base.rb 0000644 0001750 0001750 00000001361 14715406113 024551 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
class PayloadMiddleware
class Base
class << self
def middleware_name name
PayloadMiddleware.register self, name.to_sym
end
def options default_opts
@default_opts = default_opts
end
def default_opts
@default_opts ||= {}
end
end
attr_reader :messenger, :options
def initialize messenger, opts={}
@messenger = messenger
@options = self.class.default_opts.merge opts
end
def call _payload={}
raise NoMethodError, "method `call` not defined for class #{self.class}"
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/payload_middleware/channels.rb 0000644 0001750 0001750 00000000671 14715406113 025435 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
class PayloadMiddleware
class Channels < Base
middleware_name :channels
def call payload={}
return payload unless payload[:channel].respond_to?(:to_ary)
payload[:channel].to_ary.map do |channel|
pld = payload.dup
pld[:channel] = channel
pld
end
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/payload_middleware/format_attachments.rb 0000644 0001750 0001750 00000002100 14715406113 027512 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
class PayloadMiddleware
class FormatAttachments < Base
middleware_name :format_attachments
options formats: %i[html markdown]
def call payload={}
payload = payload.dup
attachments = payload.delete(:attachments)
attachments ||= payload.delete("attachments")
attachments = wrap_array(attachments).map do |attachment|
["text", :text].each do |key|
if attachment.key?(key)
attachment[key] = Util::LinkFormatter.format(attachment[key], **options)
end
end
attachment
end
payload[:attachments] = attachments if attachments && !attachments.empty?
payload
end
private
def wrap_array object
if object.nil?
[]
elsif object.respond_to?(:to_ary)
object.to_ary || [object]
else
[object]
end
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/payload_middleware/format_message.rb 0000644 0001750 0001750 00000000647 14715406113 026641 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
class PayloadMiddleware
class FormatMessage < Base
middleware_name :format_message
options formats: %i[html markdown]
def call payload={}
return payload unless payload[:text]
payload[:text] = Util::LinkFormatter.format(payload[:text], **options)
payload
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/payload_middleware/stack.rb 0000644 0001750 0001750 00000002113 14715406113 024740 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
class PayloadMiddleware
class Stack
attr_reader :messenger,
:stack
def initialize messenger
@messenger = messenger
@stack = []
end
def set *middlewares
middlewares =
if middlewares.length == 1 && middlewares.first.is_a?(Hash)
middlewares.first
else
middlewares.flatten
end
@stack = middlewares.map do |key, opts|
PayloadMiddleware.registry.fetch(key).new(*[messenger, opts].compact)
end
end
def call payload={}
result = stack.inject payload do |pld, middleware|
as_array(pld).flat_map do |p|
middleware.call(p)
end
end
as_array(result)
end
private
def as_array args
if args.respond_to?(:to_ary)
args.to_ary
else
[args]
end
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/util/ 0000775 0001750 0001750 00000000000 14715406113 020442 5 ustar pravi pravi slack-messenger-2.3.5/lib/slack-messenger/util/escape.rb 0000644 0001750 0001750 00000000507 14715406113 022227 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
module Util
module Escape
HTML_REGEXP = /[&><]/
HTML_REPLACE = { "&" => "&", ">" => ">", "<" => "<" }.freeze
def self.html string
string.gsub(HTML_REGEXP, HTML_REPLACE)
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/util/http_client.rb 0000644 0001750 0001750 00000003135 14715406113 023304 0 ustar pravi pravi # frozen_string_literal: true
require "net/http"
module Slack
class Messenger
class APIError < StandardError; end
module Util
class HTTPClient
class << self
def post uri, params
HTTPClient.new(uri, params).call
end
end
attr_reader :uri, :params, :http_options
def initialize uri, params
@uri = uri
@http_options = params.delete(:http_options) || {}
@params = params
end
# rubocop:disable Layout/IndentHeredoc
def call
http_obj.request(request_obj).tap do |response|
unless response.is_a?(Net::HTTPSuccess)
raise Slack::Messenger::APIError, <<-MSG
The slack API returned an error: #{response.body} (HTTP Code #{response.code})
Check the "Handling Errors" section on https://api.slack.com/incoming-webhooks for more information
MSG
end
end
end
# rubocop:enable Layout/IndentHeredoc
private
def request_obj
req = Net::HTTP::Post.new uri.request_uri
req.set_form_data params
req
end
def http_obj
http = Net::HTTP.new uri.host, uri.port
http.use_ssl = (uri.scheme == "https")
http_options.each do |opt, val|
if http.respond_to? "#{opt}="
http.send "#{opt}=", val
else
warn "Net::HTTP doesn't respond to `#{opt}=`, ignoring that option"
end
end
http
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/util/link_formatter.rb 0000644 0001750 0001750 00000005003 14715406113 024003 0 ustar pravi pravi # frozen_string_literal: true
require_relative 'untrusted_regexp'
module Slack
class Messenger
module Util
class LinkFormatter
# https://regex101.com/r/m7CTcW/1
HTML_PATTERN =
'.+?)[\'"]' \
'(?:.*?)>' \
'(?P.+?)' \
''
VALID_URI_CHARS = '\w\-\.\~\:\/\?\#\[\]\@\!\$\&\'\*\+\,\;\='
# Attempt at only matching pairs of parens per
# the markdown spec http://spec.commonmark.org/0.27/#links
# https://regex101.com/r/komyFe/1
MARKDOWN_PATTERN =
'\[' \
'(?P[^\[\]]*?)' \
'\]' \
'\(' \
'(?P' \
'(?:https?:\/\/|mailto:)' \
"(?:[#{VALID_URI_CHARS}]*?|[#{VALID_URI_CHARS}]*?\\([#{VALID_URI_CHARS}]*?\\)[#{VALID_URI_CHARS}]*?)" \
')' \
'\)'
HTML_REGEXP = UntrustedRegexp.new(HTML_PATTERN, multiline: true)
MARKDOWN_REGEXP = UntrustedRegexp.new(MARKDOWN_PATTERN, multiline: true)
class << self
def format string, **opts
LinkFormatter.new(string, **opts).formatted
end
end
attr_reader :formats
def initialize string, formats: %i[html markdown]
@formats = formats
@orig = string.respond_to?(:scrub) ? string.scrub : string
end
# rubocop:disable Lint/RescueWithoutErrorClass
def formatted
return @orig unless @orig.respond_to?(:gsub)
sub_markdown_links(sub_html_links(@orig))
rescue => e
raise e unless RUBY_VERSION < "2.1" && e.message.include?("invalid byte sequence")
raise e, "#{e.message}. Consider including the 'string-scrub' gem to strip invalid characters"
end
# rubocop:enable Lint/RescueWithoutErrorClass
private
def sub_html_links string
return string unless formats.include?(:html)
HTML_REGEXP.replace_gsub(string) do |html_link|
slack_link(html_link[1], html_link[2])
end
end
def sub_markdown_links string
return string unless formats.include?(:markdown)
MARKDOWN_REGEXP.replace_gsub(string) do |markdown_link|
slack_link(markdown_link[2], markdown_link[1])
end
end
def slack_link link, text=nil
"<#{link}" \
"#{text && !text.empty? ? "|#{text}" : ''}" \
">"
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/util/untrusted_regexp.rb 0000644 0001750 0001750 00000004064 14715406113 024400 0 ustar pravi pravi # frozen_string_literal: true
# An untrusted regular expression is any regexp containing patterns sourced
# from user input.
#
# Ruby's built-in regular expression library allows patterns which complete in
# exponential time, permitting denial-of-service attacks.
#
# Not all regular expression features are available in untrusted regexes, and
# there is a strict limit on total execution time. See the RE2 documentation
# at https://github.com/google/re2/wiki/Syntax for more details.
#
# This class doesn't change any instance variables, which allows it to be frozen
# and setup in constants.
#
# This class only provides support replacing matched token with a block (like `gsub`).
module Slack
class Messenger
module Util
class UntrustedRegexp
require 're2'
def initialize(pattern, multiline: false)
if multiline
pattern = "(?m)#{pattern}"
end
@regexp = RE2::Regexp.new(pattern, log_errors: false)
@scan_regexp = initialize_scan_regexp
raise RegexpError, regexp.error unless regexp.ok?
end
# There is no built-in replace with block support (like `gsub`). We can accomplish
# the same thing by parsing and rebuilding the string with the substitutions.
def replace_gsub(text)
new_text = +''
remainder = text
matched = match(remainder)
until matched.nil? || matched.to_a.compact.empty?
partitioned = remainder.partition(matched.to_s)
new_text << partitioned.first
remainder = partitioned.last
new_text << yield(matched)
matched = match(remainder)
end
new_text << remainder
end
def match(text)
scan_regexp.match(text)
end
private
attr_reader :regexp, :scan_regexp
def initialize_scan_regexp
if regexp.number_of_capturing_groups == 0
RE2::Regexp.new('(' + regexp.source + ')')
else
regexp
end
end
end
end
end
end
slack-messenger-2.3.5/lib/slack-messenger/version.rb 0000644 0001750 0001750 00000000215 14715406113 021473 0 ustar pravi pravi # frozen_string_literal: true
module Slack
class Messenger
VERSION = "2.3.5".freeze # rubocop:disable Style/RedundantFreeze
end
end
slack-messenger-2.3.5/spec/ 0000775 0001750 0001750 00000000000 14715406113 014566 5 ustar pravi pravi slack-messenger-2.3.5/spec/end_to_end_spec.rb 0000644 0001750 0001750 00000007531 14715406113 020227 0 ustar pravi pravi # frozen_string_literal: true
# encoding: utf-8
require "spec_helper"
RSpec.describe Slack::Messenger do
{
{ text: "hello" } =>
{ payload: { text: "hello" } },
{ text: "[hello](http://example.com/world)" } =>
{ payload: { text: "" } },
{ text: 'example' } =>
{ payload: { text: "" } },
{ text: "hello/こんにちは from messenger test" } =>
{ payload: { text: "hello/こんにちは from messenger test" } },
{ text: "Hello World, enjoy [](http://example.com)." } =>
{ payload: { text: "Hello World, enjoy ." } },
{ text: "Hello World, enjoy [this](http://example.com)[this2](http://example2.com)" } =>
{ payload: { text: "Hello World, enjoy " } },
{ text: "[John](mailto:john@example.com)" } =>
{ payload: { text: "" } },
{ text: 'John' } =>
{ payload: { text: "" } },
{ text: "hello", channel: "hodor" } =>
{ payload: { text: "hello", channel: "hodor" } },
{ text: nil, attachments: [{ text: "attachment message" }] } =>
{ payload: { text: nil, attachments: [{ text: "attachment message" }] } },
{ text: "the message", channel: "foo", attachments: [{ color: "#000",
text: "attachment message",
fallback: "fallback message" }] } =>
{ payload: { text: "the message",
channel: "foo",
attachments: [{ color: "#000",
text: "attachment message",
fallback: "fallback message" }] } },
{ attachments: [{ color: "#000",
text: "attachment message",
fallback: "fallback message" }] } =>
{ payload: { attachments: [{ color: "#000",
text: "attachment message",
fallback: "fallback message" }] } },
{ attachments: { color: "#000",
text: "attachment message [hodor](http://winterfell.com)",
fallback: "fallback message" } } =>
{ payload: { attachments: [{ color: "#000",
text: "attachment message ",
fallback: "fallback message" }] } },
{ attachments: { color: "#000",
text: nil,
fallback: "fallback message" } } =>
{ payload: { attachments: [{ color: "#000",
text: nil,
fallback: "fallback message" }] } },
{ text: "hello", http_options: { timeout: 5 } } =>
{ http_options: { timeout: 5 }, payload: { text: "hello" } }
}.each do |args, payload|
it "sends correct payload for #post(#{args})" do
http_client = class_double("Slack::Messenger::Util::HTTPClient", post: nil)
messenger = Slack::Messenger.new "http://example.com", http_client: http_client
payload[:payload] = payload[:payload].to_json
expect(http_client).to receive(:post)
.with(URI.parse("http://example.com"), payload)
messenger.post(args)
end
end
it "applies options given to middleware" do
client = class_double("Slack::Messenger::Util::HTTPClient", post: nil)
messenger = Slack::Messenger.new "http://example.com" do
http_client client
middleware format_message: { formats: [] }
end
expect(client).to receive(:post)
.with(URI.parse("http://example.com"), payload: { text: "Hello [world](http://example.com)!" }.to_json)
messenger.post text: "Hello [world](http://example.com)!"
end
end
slack-messenger-2.3.5/spec/integration/ 0000775 0001750 0001750 00000000000 14715406113 017111 5 ustar pravi pravi slack-messenger-2.3.5/spec/integration/ping_integration_test.rb 0000644 0001750 0001750 00000001023 14715406113 024027 0 ustar pravi pravi # frozen_string_literal: true
# encoding: utf-8
require_relative "../../lib/slack-messenger"
ruby = if defined?(JRUBY_VERSION)
"jruby #{JRUBY_VERSION}"
else
"ruby #{RUBY_VERSION}"
end
puts "testing with #{ruby}"
messenger = Slack::Messenger.new ENV["SLACK_WEBHOOK_URL"], username: "messenger"
messenger.ping "hello", channel: ["#general", "#random"]
messenger.ping "hello/こんにちは from messenger test script on #{ruby}\225"
messenger.ping attachments: [{ color: "#1BF5AF", fallback: "fallback", text: "attachment" }]
slack-messenger-2.3.5/spec/lib/ 0000775 0001750 0001750 00000000000 14715406113 015334 5 ustar pravi pravi slack-messenger-2.3.5/spec/lib/slack-messenger/ 0000775 0001750 0001750 00000000000 14715406113 020417 5 ustar pravi pravi slack-messenger-2.3.5/spec/lib/slack-messenger/config_spec.rb 0000644 0001750 0001750 00000004072 14715406113 023224 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger::Config do
describe "#http_client" do
it "is Util::HTTPClient if not set" do
subject = described_class.new
expect(subject.http_client).to eq Slack::Messenger::Util::HTTPClient
end
it "sets a new client class if given one" do
new_client = class_double("Slack::Messenger::Util::HTTPClient", post: nil)
subject = described_class.new
subject.http_client new_client
expect(subject.http_client).to eq new_client
end
it "raises an ArgumentError if given class does not respond to ::post" do
subject = described_class.new
expect do
subject.http_client :nope
end.to raise_error ArgumentError
end
end
describe "#defaults" do
it "is an empty hash by default" do
subject = described_class.new
expect(subject.defaults).to eq({})
end
it "sets a hash to default if given" do
subject = described_class.new
subject.defaults foo: :bar
expect(subject.defaults).to eq foo: :bar
end
it "raises ArgumentError if not given a hash" do
subject = described_class.new
expect do
subject.defaults :nope
end.to raise_error ArgumentError
end
end
describe "#middleware" do
it "is [:format_message, :format_attachments, :at] if not set" do
subject = described_class.new
expect(subject.middleware).to eq %i[format_message format_attachments at channels]
end
it "takes an array or a splat of args" do
subject = described_class.new
subject.middleware :layer, :two
expect(subject.middleware).to eq %i[layer two]
subject.middleware %i[one layer]
expect(subject.middleware).to eq %i[one layer]
end
it "allows passing options to middleware stack" do
subject = described_class.new
subject.middleware one: { opts: :for_one },
two: { opts: :for_two }
expect(subject.middleware).to eq one: { opts: :for_one },
two: { opts: :for_two }
end
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/payload_middleware/ 0000775 0001750 0001750 00000000000 14715406113 024245 5 ustar pravi pravi slack-messenger-2.3.5/spec/lib/slack-messenger/payload_middleware/at_spec.rb 0000644 0001750 0001750 00000001476 14715406113 026216 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger::PayloadMiddleware::At do
it "can handle array at" do
subject = described_class.new(:messenger)
payload = { text: "hello", at: %i[john ken here] }
expect(subject.call(payload)).to eq text: "<@john> <@ken> hello"
end
it "can handle single at option" do
subject = described_class.new(:messenger)
payload = { text: "hello", at: :alice }
expect(subject.call(payload)).to eq text: "<@alice> hello"
end
it "generates :text in payload if given :at & no :text" do
subject = described_class.new(:messenger)
input_payload = { at: [:here], attachments: [{ text: "hello" }] }
output_payload = { text: " ", attachments: [{ text: "hello" }] }
expect(subject.call(input_payload)).to eq output_payload
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/payload_middleware/base_spec.rb 0000644 0001750 0001750 00000004233 14715406113 026516 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger::PayloadMiddleware::Base do
before(:each) do
@registry_backup = Slack::Messenger::PayloadMiddleware.registry.dup
Slack::Messenger::PayloadMiddleware.send(:remove_instance_variable, :@registry)
end
after(:each) do
# cleanup middleware registry
Slack::Messenger::PayloadMiddleware.registry
Slack::Messenger::PayloadMiddleware.send(:remove_instance_variable, :@registry)
# cleanup object constants
Object.send(:remove_const, :Subject) if Object.constants.include?(:Subject)
Slack::Messenger::PayloadMiddleware.send(:instance_variable_set, :@registry, @registry_backup)
end
describe "::middleware_name" do
it "registers class w/ given name" do
class Subject < Slack::Messenger::PayloadMiddleware::Base
end
expect(Slack::Messenger::PayloadMiddleware)
.to receive(:register).with(Subject, :subject)
class Subject
middleware_name :subject
end
end
it "uses symbolized name to register" do
class Subject < Slack::Messenger::PayloadMiddleware::Base
end
expect(Slack::Messenger::PayloadMiddleware)
.to receive(:register).with(Subject, :subject)
class Subject
middleware_name "subject"
end
end
end
describe "::options" do
it "allows setting default options for a middleware" do
class Subject < Slack::Messenger::PayloadMiddleware::Base
options foo: :bar
end
subject = Subject.new(:messenger)
expect(subject.options).to eq foo: :bar
subject = Subject.new(:messenger, foo: :baz)
expect(subject.options).to eq foo: :baz
end
end
describe "#initialize" do
it "sets given messenger as messenger" do
expect(described_class.new(:messenger).messenger).to eq :messenger
end
it "sets given options as opts" do
expect(described_class.new(:messenger, opts: :options).options).to eq opts: :options
end
end
describe "#call" do
it "raises NoMethodError (expects subclass to define)" do
expect do
described_class.new(:messenger).call
end.to raise_exception NoMethodError
end
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/payload_middleware/channels_spec.rb 0000644 0001750 0001750 00000001157 14715406113 027401 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger::PayloadMiddleware::Channels do
it "leaves string channels alone" do
subject = described_class.new(:messenger)
payload = { text: "hello", channel: "hodor" }
expect(subject.call(payload)).to eq text: "hello", channel: "hodor"
end
it "splits payload into multiple if given an array of channels" do
subject = described_class.new(:messenger)
payload = { text: "hello", channel: %w[foo hodor] }
expect(subject.call(payload)).to eq [
{ text: "hello", channel: "foo" },
{ text: "hello", channel: "hodor" }
]
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/payload_middleware/format_attachments_spec.rb 0000644 0001750 0001750 00000003176 14715406113 031474 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger::PayloadMiddleware::FormatAttachments do
it "passes the text of attachments through linkformatter with options[:formats]" do
subject = described_class.new(:messenger, formats: [:html])
expect(Slack::Messenger::Util::LinkFormatter).to receive(:format)
.with("hello", formats: [:html])
subject.call(attachments: [{ text: "hello" }])
end
it "searches through string or symbol keys" do
subject = described_class.new(:messenger)
expect(Slack::Messenger::Util::LinkFormatter).to receive(:format)
.with("hello", formats: %i[html markdown])
subject.call("attachments" => [{ "text" => "hello" }])
subject = described_class.new(:messenger)
expect(Slack::Messenger::Util::LinkFormatter).to receive(:format)
.with("hello", formats: %i[html markdown])
subject.call(attachments: [{ text: "hello" }])
end
it "can handle a single attachment" do
subject = described_class.new(:messenger)
expect(Slack::Messenger::Util::LinkFormatter).to receive(:format)
.with("hello", formats: %i[html markdown])
subject.call(attachments: { text: "hello" })
end
it "wraps attachment into array if given as a single hash" do
params = {
attachments: { text: "hello" }
}
payload = {
attachments: [{ text: "hello" }]
}
subject = described_class.new(:messenger)
expect(subject.call(params)).to eq payload
end
it "returns the payload unmodified if not :attachments key" do
payload = { foo: :bar }
subject = described_class.new(:messenger)
expect(subject.call(payload)).to eq payload
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/payload_middleware/format_message_spec.rb 0000644 0001750 0001750 00000001732 14715406113 030601 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger::PayloadMiddleware::FormatMessage do
it "passes the text through linkformatter with options[:formats]" do
subject = described_class.new(:messenger, formats: [:html])
expect(Slack::Messenger::Util::LinkFormatter).to receive(:format)
.with("hello", formats: [:html])
subject.call(text: "hello")
subject = described_class.new(:messenger)
expect(Slack::Messenger::Util::LinkFormatter).to receive(:format)
.with("hello", formats: %i[html markdown])
subject.call(text: "hello")
subject = described_class.new(:messenger, formats: [:markdown])
expect(Slack::Messenger::Util::LinkFormatter).to receive(:format)
.with("hello", formats: [:markdown])
subject.call(text: "hello")
end
it "returns the payload unmodified if not :text key" do
payload = { foo: :bar }
subject = described_class.new(:messenger)
expect(subject.call(payload)).to eq payload
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/payload_middleware/stack_spec.rb 0000644 0001750 0001750 00000007562 14715406113 026721 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger::PayloadMiddleware::Stack do
let(:return_one) do
double(call: 1)
end
let(:return_one_twice) do
double(call: [1, 1])
end
let(:return_two) do
double(call: 2)
end
let(:return_three) do
double(call: 3)
end
before(:each) do
# setup our middleware in the registry
@registry_backup = Slack::Messenger::PayloadMiddleware.registry.dup
Slack::Messenger::PayloadMiddleware.send(:remove_instance_variable, :@registry)
Slack::Messenger::PayloadMiddleware.register return_one, :return_one
Slack::Messenger::PayloadMiddleware.register return_one_twice, :return_one_twice
Slack::Messenger::PayloadMiddleware.register return_two, :return_two
Slack::Messenger::PayloadMiddleware.register return_three, :return_three
end
after(:each) do
# cleanup middleware registry
Slack::Messenger::PayloadMiddleware.send(:remove_instance_variable, :@registry)
Slack::Messenger::PayloadMiddleware.send(:instance_variable_set, :@registry, @registry_backup)
end
describe "::initialize" do
it "sets messenger to given messenger" do
expect(described_class.new(:messenger).messenger).to eq :messenger
end
it "has empty stack" do
expect(described_class.new(:messenger).stack).to match_array []
end
end
describe "#set" do
it "initializes each middleware w/ the messenger instance" do
expect(return_one).to receive(:new).with(:messenger)
expect(return_two).to receive(:new).with(:messenger)
described_class.new(:messenger).set(:return_one, :return_two)
end
it "creates the stack in an array" do
allow(return_one).to receive(:new).and_return(return_one)
allow(return_two).to receive(:new).and_return(return_two)
subject = described_class.new(:messenger)
subject.set(:return_one, :return_two)
expect(subject.stack).to be_a Array
expect(subject.stack.first.call).to eq 1
expect(subject.stack.last.call).to eq 2
end
it "creates a stack from hashes passing them as opts" do
expect(return_one).to receive(:new).with(:messenger, opts: :for_one)
expect(return_two).to receive(:new).with(:messenger, opts: :for_two)
subject = described_class.new(:messenger)
subject.set return_one: { opts: :for_one },
return_two: { opts: :for_two }
end
it "raises if a middleware is missing" do
expect do
described_class.new(:messenger).set(:missing)
end.to raise_exception KeyError
end
end
describe "#call" do
it "calls the middleware in order, passing return of each to the next" do
allow(return_one).to receive(:new).and_return(return_one)
allow(return_two).to receive(:new).and_return(return_two)
allow(return_three).to receive(:new).and_return(return_three)
subject = described_class.new(:messenger)
subject.set(:return_one, :return_three, :return_two)
expect(return_one).to receive(:call).with(5)
expect(return_three).to receive(:call).with(1)
expect(return_two).to receive(:call).with(3)
expect(subject.call(5)).to eq [2]
end
it "allows any middleware to return an array but other's don't need special behavior" do
allow(return_one_twice).to receive(:new).and_return(return_one_twice)
allow(return_two).to receive(:new).and_return(return_two)
subject = described_class.new(:messenger)
subject.set(:return_one_twice, :return_two)
expect(subject.call(5)).to eq [2, 2]
end
it "handles multiple middleware splitting payload" do
allow(return_one_twice).to receive(:new).and_return(return_one_twice)
allow(return_two).to receive(:new).and_return(return_two)
subject = described_class.new(:messenger)
subject.set(:return_one_twice, :return_one_twice, :return_two)
expect(subject.call(5)).to eq [2, 2, 2, 2]
end
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/payload_middleware_spec.rb 0000644 0001750 0001750 00000001714 14715406113 025605 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger::PayloadMiddleware do
before(:each) do
@registry_backup = described_class.registry.dup
Slack::Messenger::PayloadMiddleware.send(:remove_instance_variable, :@registry)
end
after(:each) do
described_class.send(:remove_instance_variable, :@registry)
described_class.send(:instance_variable_set, :@registry, @registry_backup)
end
describe "::registry" do
it "returns a hash if nothing set" do
expect(described_class.registry).to eq({})
end
it "returns memoized version if already set" do
described_class.instance_variable_set(:@registry, "hodor")
expect(described_class.registry).to eq "hodor"
end
end
describe "::register" do
it "adds given class to key in registry" do
MyClass = Struct.new(:myclass)
described_class.register MyClass, :my_class
expect(described_class.registry[:my_class]).to eq MyClass
end
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/untrusted_regexp_spec.rb 0000644 0001750 0001750 00000003175 14715406113 025371 0 ustar pravi pravi # frozen_string_literal: true
# require 'fast_spec_helper'
RSpec.describe Slack::Messenger::Util::UntrustedRegexp do
def create_regex(regex_str, multiline: false)
described_class.new(regex_str, multiline: multiline).freeze
end
describe '#initialize' do
subject { described_class.new(pattern) }
context 'invalid regexp' do
let(:pattern) { '[' }
it { expect { subject }.to raise_error(RegexpError) }
end
end
describe '#replace_gsub' do
let(:regex_str) { '(?P(ftp))' }
let(:regex) { create_regex(regex_str, multiline: true) }
def result(regex, text)
regex.replace_gsub(text) do |match|
if match[:scheme]
"http|#{match[:scheme]}|rss"
else
match.to_s
end
end
end
it 'replaces all instances of the match in a string' do
text = 'Use only https instead of ftp'
expect(result(regex, text)).to eq('Use only https instead of http|ftp|rss')
end
it 'replaces nothing when no match' do
text = 'Use only https instead of gopher'
expect(result(regex, text)).to eq(text)
end
it 'handles empty text' do
text = ''
expect(result(regex, text)).to eq('')
end
end
describe '#match' do
context 'when there are matches' do
it 'returns a match object' do
result = create_regex('(?P\d+)').match('hello 10')
expect(result[:number]).to eq('10')
end
end
context 'when there are no matches' do
it 'returns nil' do
result = create_regex('(?P\d+)').match('hello')
expect(result).to be_nil
end
end
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/util/ 0000775 0001750 0001750 00000000000 14715406113 021374 5 ustar pravi pravi slack-messenger-2.3.5/spec/lib/slack-messenger/util/http_client_spec.rb 0000644 0001750 0001750 00000003721 14715406113 025251 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger::Util::HTTPClient do
describe "::post" do
it "initializes Util::HTTPClient with the given uri and params then calls" do
http_post_double = instance_double("Slack::Messenger::Util::HTTPClient")
expect(described_class)
.to receive(:new).with("uri", "params")
.and_return(http_post_double)
expect(http_post_double).to receive(:call)
described_class.post "uri", "params"
end
# http_post is really tested in the integration spec,
# where the internals are run through
end
describe "#initialize" do
it "allows setting of options for Net::HTTP" do
net_http_double = instance_double("Net::HTTP")
http_client = described_class.new URI.parse("http://example.com"),
http_options: { open_timeout: 5 }
allow(Net::HTTP).to receive(:new).and_return(net_http_double)
allow(net_http_double).to receive(:use_ssl=)
allow(net_http_double).to receive(:request).with(anything) do
Net::HTTPOK.new("GET", "200", "OK")
end
expect(net_http_double).to receive(:open_timeout=).with(5)
http_client.call
end
end
describe "#call" do
it "raises an error when the response is unsuccessful" do
net_http_double = instance_double("Net::HTTP")
http_client = described_class.new URI.parse("http://example.com"), {}
bad_request = Net::HTTPBadRequest.new("GET", "400", "Bad Request")
allow(bad_request).to receive(:body).and_return("something_bad")
allow(Net::HTTP).to receive(:new).and_return(net_http_double)
allow(net_http_double).to receive(:use_ssl=)
allow(net_http_double).to receive(:request).with(anything) do
bad_request
end
expect { http_client.call }.to raise_error(Slack::Messenger::APIError,
/something_bad \(HTTP Code 400\)/)
end
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger/util/link_formatter_spec.rb 0000644 0001750 0001750 00000017122 14715406113 025754 0 ustar pravi pravi # frozen_string_literal: true
# encoding: utf-8
# rubocop:disable Metrics/LineLength
RSpec.describe Slack::Messenger::Util::LinkFormatter do
describe "::format" do
it "formats html links" do
formatted = described_class.format("Hello World, enjoy this.")
expect(formatted).to include("")
end
it "formats markdown links" do
formatted = described_class.format("Hello World, enjoy [this](http://example.com).")
expect(formatted).to include("")
end
it "formats markdown links in brackets" do
formatted = described_class.format("Hello World, enjoy [[this](http://example.com) in brackets].")
expect(formatted).to eq("Hello World, enjoy [ in brackets].")
end
it "formats markdown links with no title" do
formatted = described_class.format("Hello World, enjoy [](http://example.com).")
expect(formatted).to include("")
end
it "handles multiple html links" do
formatted = described_class.format("Hello World, enjoy thisthis2.")
expect(formatted).to include("")
expect(formatted).to include("")
end
it "handles multiple markdown links" do
formatted = described_class.format("Hello World, enjoy [this](http://example.com)[this2](http://example2.com).")
expect(formatted).to include("")
expect(formatted).to include("")
end
it "handles mixed html & markdown links" do
formatted = described_class.format("Hello World, enjoy [this](http://example.com)this2.")
expect(formatted).to include("")
expect(formatted).to include("")
end
if "".respond_to? :scrub
context "when on ruby 2.1+ or have string-scrub installed" do
it "handles invalid unicode sequences" do
expect do
described_class.format("This sequence is invalid: \255")
end.not_to raise_error
end
it "replaces invalid unicode sequences with the unicode replacement character" do
formatted = described_class.format("\255")
expect(formatted).to eq "\uFFFD"
end
end
end
it "doesn't replace valid Japanese" do
formatted = described_class.format("こんにちは")
expect(formatted).to eq "こんにちは"
end
it "handles mailto links in markdown" do
formatted = described_class.format("[John](mailto:john@example.com)")
expect(formatted).to eq ""
end
it "handles mailto links in html" do
formatted = described_class.format("John")
expect(formatted).to eq ""
end
it "handles links with trailing parentheses" do
formatted = described_class.format("Hello World, enjoy [foo(bar)](http://example.com/foo(bar))bar(foo)")
expect(formatted).to include("http://example.com/foo(bar)|foo(bar)")
expect(formatted).to include("http://example.com/bar(foo)|bar(foo)")
end
it "formats a number of differently formatted links" do
input_output = {
"Hello World, enjoy [this](http://example.com)." =>
"Hello World, enjoy .",
"Hello World, enjoy [[this](http://example.com) in brackets]." =>
"Hello World, enjoy [ in brackets].",
"Hello World, enjoy ([this](http://example.com) in parens)." =>
"Hello World, enjoy ( in parens).",
"Hello World, enjoy ([this](http://example.com))." =>
"Hello World, enjoy ().",
"Hello World, enjoy [](http://example.com)." =>
"Hello World, enjoy .",
"Hello World, enjoy [link with query](http://example.com?foo=bar)." =>
"Hello World, enjoy .",
"Hello World, enjoy [link with fragment](http://example.com/#foo-bar)." =>
"Hello World, enjoy .",
"Hello World, enjoy [link with parens](http://example.com/foo(bar)/baz)." =>
"Hello World, enjoy .",
"Hello World, enjoy [link with query](http://example.com/(parens)?foo=bar)." =>
"Hello World, enjoy .",
"Hello World, enjoy [link with parens](http://example.com/baz?bang=foo(bar))." =>
"Hello World, enjoy .",
"Hello World, enjoy [link with fragment](http://example.com/(parens)#foo-bar)." =>
"Hello World, enjoy .",
"Hello World, enjoy [link with fragment](http://example.com/#foo-bar=(baz))." =>
"Hello World, enjoy .",
"Hello World, enjoy [this](http://example.com?foo=bar)[this2](http://example2.com)." =>
"Hello World, enjoy .",
"Hello World, enjoy [this](http://example.com?foo=bar) [this2](http://example2.com/#fragment)." =>
"Hello World, enjoy .",
"Hello World, enjoy [this](http://example.com)this2." =>
"Hello World, enjoy .",
"Hello world, [John](mailto:john@example.com)." =>
"Hello world, .",
"Hello World, enjoy [foo(bar)](http://example.com/foo(bar))bar(foo)" =>
"Hello World, enjoy "
}
input_output.each do |input, output|
expect(described_class.format(input)).to eq output
end
end
context "with a configured stack" do
it "only formats html if html is the only item in formats" do
formatted = described_class.format("Hello World, enjoy [this](http://example.com)this2.", formats: [:html])
expect(formatted).to eq "Hello World, enjoy [this](http://example.com)."
end
it "only formats markdown if markdown is the only item in formats" do
formatted = described_class.format("Hello World, enjoy [this](http://example.com)this2.", formats: [:markdown])
expect(formatted).to eq "Hello World, enjoy this2."
end
it "doesn't format if formats is empty" do
formatted = described_class.format("Hello World, enjoy [this](http://example.com)this2.", formats: [])
expect(formatted).to eq "Hello World, enjoy [this](http://example.com)this2."
end
end
context "handles malicious strings efficiently" do
let(:malicious_text) { "![" * 500000 }
subject(:format) { described_class.format(malicious_text) }
it "takes under a second" do
expect { Timeout.timeout(1) { subject } }.not_to raise_error
end
end
end
end
slack-messenger-2.3.5/spec/lib/slack-messenger_spec.rb 0000644 0001750 0001750 00000005602 14715406113 021757 0 ustar pravi pravi # frozen_string_literal: true
RSpec.describe Slack::Messenger do
let(:mock_http) do
class_double("Slack::Messenger::Util::HTTPClient", post: :posted)
end
subject { described_class.new "http://example.com", http_client: mock_http }
describe "#initialize" do
it "sets the given hook_url to the endpoint URI" do
expect(subject.endpoint).to eq URI.parse("http://example.com")
end
it "sets the default_payload options" do
subject = described_class.new "http://example.com", channel: "foo"
expect(subject.config.defaults[:channel]).to eq "foo"
end
it "sets a custom http client" do
subject = described_class.new "http://example.com", http_client: mock_http
expect(subject.config.http_client).to eq mock_http
end
describe "when given a block" do
it "yields the config object" do
test_double = double("Slack::Messenger::Config", defaults: {}, middleware: [])
allow_any_instance_of(Slack::Messenger).to receive(:config).and_return(test_double)
expect(test_double).to receive(:test_init_method).with("foo")
described_class.new "http://example.com" do
test_init_method "foo"
end
end
end
end
describe "#ping" do
it "calls #post with the message as the text key in #post" do
subject = described_class.new "http://example.com"
expect(subject).to receive(:post).with text: "message"
subject.ping "message"
end
end
describe "#post" do
def messenger_with_defaults
mock_client = mock_http
described_class.new "http://example.com" do
defaults channel: "default",
user: "rocket"
http_client mock_client
end
end
it "uses the defaults set on initialization" do
subject = messenger_with_defaults
expect(mock_http).to receive(:post).with(
URI.parse("http://example.com"),
payload: '{"channel":"default","user":"rocket","text":"hello"}'
)
subject.post text: "hello"
end
it "allows overriding the set defaults" do
subject = messenger_with_defaults
expect(mock_http).to receive(:post).with(
URI.parse("http://example.com"),
payload: '{"channel":"new","user":"ship","text":"hello"}'
)
subject.post text: "hello", channel: "new", user: "ship"
end
it "calls the middleware stack with the payload" do
subject = messenger_with_defaults
stack = instance_double("Slack::Messenger::PayloadMiddleware::Stack")
subject.instance_variable_set(:@middleware, stack)
expect(stack).to receive(:call)
.with(channel: "default", user: "rocket")
.and_return([test: "stack"])
expect(mock_http).to receive(:post).with(
URI.parse("http://example.com"),
payload: '{"test":"stack"}'
)
responses = subject.post
expect(responses).to eq([:posted])
end
end
end
slack-messenger-2.3.5/spec/spec_helper.rb 0000644 0001750 0001750 00000001354 14715406113 017405 0 ustar pravi pravi # frozen_string_literal: true
require "rspec"
require "slack-messenger"
require "pry" if ENV["DEBUG"]
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_doubled_constant_names = true
mocks.verify_partial_doubles = true
end
config.filter_run :focus
config.run_all_when_everything_filtered = true
config.disable_monkey_patching!
config.example_status_persistence_file_path = "spec/examples.txt"
config.warnings = ENV["DEBUG"] ? false : true
config.default_formatter = "doc" if config.files_to_run.one?
config.order = :random
Kernel.srand config.seed
end
slack-messenger-2.3.5/slack-messenger.gemspec 0000664 0001750 0001750 00000006656 14715406113 020301 0 ustar pravi pravi #########################################################
# This file has been automatically generated by gem2tgz #
#########################################################
# -*- encoding: utf-8 -*-
# stub: slack-messenger 2.3.5 ruby lib
Gem::Specification.new do |s|
s.name = "slack-messenger".freeze
s.version = "2.3.5"
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib".freeze]
s.authors = ["Steven Sloan".freeze]
s.date = "2024-06-19"
s.description = " A slim ruby wrapper for posting to slack webhooks ".freeze
s.email = ["stevenosloan@gmail.com".freeze]
s.files = ["lib/slack-messenger.rb".freeze, "lib/slack-messenger/config.rb".freeze, "lib/slack-messenger/payload_middleware.rb".freeze, "lib/slack-messenger/payload_middleware/at.rb".freeze, "lib/slack-messenger/payload_middleware/base.rb".freeze, "lib/slack-messenger/payload_middleware/channels.rb".freeze, "lib/slack-messenger/payload_middleware/format_attachments.rb".freeze, "lib/slack-messenger/payload_middleware/format_message.rb".freeze, "lib/slack-messenger/payload_middleware/stack.rb".freeze, "lib/slack-messenger/util/escape.rb".freeze, "lib/slack-messenger/util/http_client.rb".freeze, "lib/slack-messenger/util/link_formatter.rb".freeze, "lib/slack-messenger/util/untrusted_regexp.rb".freeze, "lib/slack-messenger/version.rb".freeze, "spec/end_to_end_spec.rb".freeze, "spec/integration/ping_integration_test.rb".freeze, "spec/lib/slack-messenger/config_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/at_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/base_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/channels_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/format_attachments_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/format_message_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/stack_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware_spec.rb".freeze, "spec/lib/slack-messenger/untrusted_regexp_spec.rb".freeze, "spec/lib/slack-messenger/util/http_client_spec.rb".freeze, "spec/lib/slack-messenger/util/link_formatter_spec.rb".freeze, "spec/lib/slack-messenger_spec.rb".freeze, "spec/spec_helper.rb".freeze]
s.homepage = "https://gitlab.com/gitlab-org/slack-notifier".freeze
s.licenses = ["MIT".freeze]
s.rubygems_version = "3.4.20".freeze
s.summary = "A slim ruby wrapper for posting to slack webhooks".freeze
s.test_files = ["spec/end_to_end_spec.rb".freeze, "spec/integration/ping_integration_test.rb".freeze, "spec/lib/slack-messenger/config_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/at_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/base_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/channels_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/format_attachments_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/format_message_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware/stack_spec.rb".freeze, "spec/lib/slack-messenger/payload_middleware_spec.rb".freeze, "spec/lib/slack-messenger/untrusted_regexp_spec.rb".freeze, "spec/lib/slack-messenger/util/http_client_spec.rb".freeze, "spec/lib/slack-messenger/util/link_formatter_spec.rb".freeze, "spec/lib/slack-messenger_spec.rb".freeze, "spec/spec_helper.rb".freeze]
s.specification_version = 4
s.add_runtime_dependency(%q.freeze, ["= 2.7.0"])
end