waylon-core 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0381c0f70f922e5958355430bef0ec1c8fd5d966b77b032f866fa9ecdcd3dd18'
4
- data.tar.gz: 13b63cef7b5e7575b5d564d43cb599bd326e0b9ed499be5df1b08a1c191fc073
3
+ metadata.gz: bf4a171a8420b5e982d54cf24b991a7ae667f3aa93f08b361d10ac28d9726207
4
+ data.tar.gz: 63982b47f1c05134c4fa5c69d07c4e05e8a1d748c245a4d7bd0695dabe4171c5
5
5
  SHA512:
6
- metadata.gz: bf835c89b14aa09e591fd8ca11b7c27065d0ad8262c2248ca261d897f320c0c5128d15154454407c32d35021dead332bcfb0597507e337dd7ee9de186bea2f1f
7
- data.tar.gz: 2eb7a29a02dcc44c85ed8dd0c74999299dae4ed1bb2736d6d076e9c1885ea80b96ffdc71288f730fe68de5c2487e23183d6adf0239f23112bec2cf9cf2e006ed
6
+ metadata.gz: 17a2baf807fd68312c76da6d9d1ca328ed5e5fec065711a7b8ce14a7d86850843d3ce2f6cfa3d4ea26f3c3ce30f329796427deeb98dcd6df77ec181da0afaf3f
7
+ data.tar.gz: 3edb93a7e82655d44d553687493d11a0a7559b6ae318ffb92051d481872c882baa2c6191a88e96ddd3936df278844e1caee97837f15523fd2158275d0a84eb03
data/.rubocop.yml CHANGED
@@ -21,7 +21,10 @@ Gemspec/RequireMFA:
21
21
  Enabled: false
22
22
 
23
23
  Metrics/AbcSize:
24
- Max: 19
24
+ Max: 19.5
25
+
26
+ Metrics/ClassLength:
27
+ Max: 200
25
28
 
26
29
  Metrics/CyclomaticComplexity:
27
30
  Max: 9
@@ -32,7 +35,11 @@ Metrics/PerceivedComplexity:
32
35
  Metrics/MethodLength:
33
36
  Max: 20
34
37
 
38
+ Metrics/ParameterLists:
39
+ Max: 6
40
+
35
41
  Metrics/BlockLength:
36
42
  Exclude:
37
43
  - "**/*_spec.rb"
38
44
  - "*.gemspec"
45
+ - "lib/waylon/rspec/matchers/**/*.rb"
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.3
1
+ 3.1.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- waylon-core (0.1.1)
4
+ waylon-core (0.1.2)
5
5
  addressable (~> 2.8)
6
6
  faraday (~> 1.8)
7
7
  i18n (~> 1.8)
@@ -17,7 +17,7 @@ GEM
17
17
  public_suffix (>= 2.0.2, < 5.0)
18
18
  ast (2.4.2)
19
19
  concurrent-ruby (1.1.9)
20
- diff-lcs (1.4.4)
20
+ diff-lcs (1.5.0)
21
21
  docile (1.4.0)
22
22
  faraday (1.9.3)
23
23
  faraday-em_http (~> 1.0)
@@ -53,7 +53,7 @@ GEM
53
53
  ruby2_keywords (~> 0.0.1)
54
54
  nio4r (2.5.8)
55
55
  parallel (1.21.0)
56
- parser (3.0.3.2)
56
+ parser (3.1.0.0)
57
57
  ast (~> 2.4.1)
58
58
  public_suffix (4.0.6)
59
59
  puma (5.5.2)
@@ -87,20 +87,20 @@ GEM
87
87
  diff-lcs (>= 1.2.0, < 2.0)
88
88
  rspec-support (~> 3.10.0)
89
89
  rspec-support (3.10.3)
90
- rubocop (1.23.0)
90
+ rubocop (1.24.1)
91
91
  parallel (~> 1.10)
92
92
  parser (>= 3.0.0.0)
93
93
  rainbow (>= 2.2.2, < 4.0)
94
94
  regexp_parser (>= 1.8, < 3.0)
95
95
  rexml
96
- rubocop-ast (>= 1.12.0, < 2.0)
96
+ rubocop-ast (>= 1.15.1, < 2.0)
97
97
  ruby-progressbar (~> 1.7)
98
98
  unicode-display_width (>= 1.4.0, < 3.0)
99
- rubocop-ast (1.15.0)
99
+ rubocop-ast (1.15.1)
100
100
  parser (>= 3.0.1.1)
101
101
  rubocop-rake (0.6.0)
102
102
  rubocop (~> 1.0)
103
- rubocop-rspec (2.6.0)
103
+ rubocop-rspec (2.7.0)
104
104
  rubocop (~> 1.19)
105
105
  ruby-progressbar (1.11.0)
106
106
  ruby2_keywords (0.0.5)
@@ -127,7 +127,7 @@ PLATFORMS
127
127
  arm64-darwin-21
128
128
 
129
129
  DEPENDENCIES
130
- bundler (~> 2.2)
130
+ bundler (~> 2.3)
131
131
  rake (~> 13.0)
132
132
  rspec (~> 3.10)
133
133
  rubocop (~> 1.23)
@@ -138,4 +138,4 @@ DEPENDENCIES
138
138
  yard (~> 0.9, >= 0.9.27)
139
139
 
140
140
  BUNDLED WITH
141
- 2.2.32
141
+ 2.3.4
@@ -10,19 +10,37 @@ module Waylon
10
10
  # @param action [Symbol] The method to call if the condition matches
11
11
  # @param allowed_groups [Array<Symbol>] The group names allowed to use this action
12
12
  # @param help [String] Optional help text to describe usage for this action
13
- def initialize(mechanism, action, allowed_groups, help = nil)
13
+ # @param mention_only [Boolean] Only applies to messages that directly mention (or IM) this bot
14
+ # rubocop:disable Style/OptionalBooleanParameter
15
+ def initialize(mechanism, action, allowed_groups, help = nil, mention_only = true)
14
16
  @mechanism = mechanism
15
17
  @action = action
16
18
  @allowed_groups = allowed_groups
17
19
  @help = help
20
+ @mention_only = mention_only
18
21
  end
22
+ # rubocop:enable Style/OptionalBooleanParameter
19
23
 
20
24
  # Placeholder for determining if this condition applies to the given input
21
25
  # @param _input [Waylon::Message] The input message
26
+ # @return [Boolean]
22
27
  def matches?(_input)
23
28
  false
24
29
  end
25
30
 
31
+ # Is this condition only valid for Messages that directly mention the bot?
32
+ # @return [Boolean]
33
+ def mention_only?
34
+ @mention_only
35
+ end
36
+
37
+ # Placeholder for optionally providing _named_ tokens
38
+ # @param _input [String] The message content
39
+ # @return [Hash<String,Object>]
40
+ def named_tokens(_input)
41
+ {}
42
+ end
43
+
26
44
  # Checks if a user is allowed based on this condition
27
45
  # @param user [Waylon::User] abstract user
28
46
  def permits?(user)
@@ -44,6 +62,13 @@ module Waylon
44
62
  permitted
45
63
  end
46
64
 
65
+ # Determines of a message complies with the {#mention_only?} setting for this condition
66
+ # @param message [Waylon::Message] The received message
67
+ # @return [Boolean]
68
+ def properly_mentions?(message)
69
+ (mention_only? && message.to_bot?) || (!mention_only? && !message.to_bot?)
70
+ end
71
+
47
72
  # Tokens is used to provide details about the message input to the action
48
73
  # @param _input [String] The message content as text
49
74
  # @return [Array<String>] The tokens extracted from the input message
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Waylon
4
+ module Conditions
5
+ # A pre-made catch-all condition for ignoring messages
6
+ class BlackHole < Condition
7
+ # Overrides normal Condition initialization to force a specific action
8
+ def initialize(*_args) # rubocop:disable Lint/MissingSuper
9
+ @mechanism = nil
10
+ @action = :ignore
11
+ @allowed_groups = [:everyone]
12
+ @help = ""
13
+ end
14
+
15
+ # Matches any input (since the Default route, when used, should always function)
16
+ # @return [Boolean]
17
+ def matches?(_input)
18
+ true
19
+ end
20
+
21
+ # Permits any user (since the Default route, when used, should always function)
22
+ # @return [Boolean]
23
+ def permits?(_user)
24
+ true
25
+ end
26
+
27
+ # Just provides back all input as a single token
28
+ # @return [Array<String>]
29
+ def tokens(input)
30
+ [input]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -17,6 +17,11 @@ module Waylon
17
17
  def tokens(input)
18
18
  @mechanism.match(input).to_a[1..]
19
19
  end
20
+
21
+ def named_tokens(input)
22
+ match_data = @mechanism.match(input)
23
+ match_data.names.to_h { |n| [n.to_sym, match_data[n]] }
24
+ end
20
25
  end
21
26
  end
22
27
  end
data/lib/waylon/config.rb CHANGED
@@ -23,7 +23,7 @@ module Waylon
23
23
  true
24
24
  end
25
25
 
26
- # A list of emails specified via the CONF_GLOBAL_ADMINS environment variable
26
+ # A list of emails specified via the GLOBAL_ADMINS environment variable
27
27
  # @return [Array<String>] a list of emails
28
28
  def admins
29
29
  admin_emails = self["global.admins"]
@@ -32,11 +32,12 @@ module Waylon
32
32
 
33
33
  # Load in the config from env variables
34
34
  # @return [Boolean] Was the configuration loaded?
35
- def load_env
35
+ def load_env # rubocop:disable Metrics/AbcSize
36
36
  @schema ||= {}
37
37
  self["global.log.level"] = ENV.fetch("LOG_LEVEL", "info")
38
38
  self["global.redis.host"] = ENV.fetch("REDIS_HOST", "redis")
39
39
  self["global.redis.port"] = ENV.fetch("REDIS_PORT", "6379")
40
+ self["global.admins"] = ENV.fetch("GLOBAL_ADMINS", "")
40
41
  ENV.keys.grep(/CONF_/).each do |env_key|
41
42
  conf_key = env_key.downcase.split("_")[1..].join(".")
42
43
  ::Waylon::Logger.log("Attempting to set #{conf_key} from #{env_key}", :debug)
data/lib/waylon/core.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  # Standard Library dependencies
4
4
  require "base64"
5
+ require "benchmark"
5
6
  require "digest"
6
7
  require "English"
7
8
  require "fileutils"
@@ -21,6 +22,7 @@ require "i18n"
21
22
  require "json"
22
23
  require "moneta"
23
24
  require "resque"
25
+ require "sinatra"
24
26
 
25
27
  # Internal requirements
26
28
  require "waylon/version"
@@ -39,8 +41,11 @@ require "waylon/sense"
39
41
  require "waylon/skill_registry"
40
42
  require "waylon/skill"
41
43
  require "waylon/user"
44
+ require "waylon/conditions/black_hole"
42
45
  require "waylon/conditions/default"
43
46
  require "waylon/conditions/permission_denied"
44
47
  require "waylon/conditions/regex"
48
+ require "waylon/routes/black_hole"
45
49
  require "waylon/routes/default"
46
50
  require "waylon/routes/permission_denied"
51
+ require "waylon/webhook"
data/lib/waylon/group.rb CHANGED
@@ -30,7 +30,7 @@ module Waylon
30
30
  return false unless include?(user)
31
31
 
32
32
  users = members
33
- users.delete(user)
33
+ users.delete(user.email.downcase)
34
34
  storage.store(key, users)
35
35
  true
36
36
  end
data/lib/waylon/logger.rb CHANGED
@@ -6,7 +6,7 @@ module Waylon
6
6
  # The log level as defined in the global Config singleton
7
7
  # @return [String] The current log level
8
8
  def self.level
9
- Config.instance["global.log.level"]
9
+ Config.instance["global.log.level"] || "info"
10
10
  end
11
11
 
12
12
  # Abstraction for sending logs to the logger at some level
@@ -9,9 +9,28 @@ module Waylon
9
9
  nil
10
10
  end
11
11
 
12
+ # Message body
13
+ def body
14
+ nil
15
+ end
16
+
12
17
  # Message channel (meant to be overwritten by mixing classes)
13
18
  def channel
14
19
  nil
15
20
  end
21
+
22
+ # Does the Message mention the bot (meant to be overwritten by mixing classes)
23
+ def mentions_bot?
24
+ nil
25
+ end
26
+
27
+ # Is the Message a private/direct Message?
28
+ def private?
29
+ false
30
+ end
31
+
32
+ def to_bot?
33
+ true
34
+ end
16
35
  end
17
36
  end
data/lib/waylon/route.rb CHANGED
@@ -23,7 +23,7 @@ module Waylon
23
23
  @priority = priority
24
24
  end
25
25
 
26
- delegate %i[action help matches? permits? tokens] => :@condition
26
+ delegate %i[action help matches? mention_only? named_tokens permits? properly_mentions? tokens] => :@condition
27
27
 
28
28
  private
29
29
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Waylon
4
+ module Routes
5
+ # The route for unroutable events
6
+ class BlackHole < Route
7
+ def initialize(
8
+ name: "black_hole",
9
+ destination: Skills::Default,
10
+ condition: Conditions::BlackHole.new,
11
+ priority: 0
12
+ )
13
+ super
14
+ end
15
+ end
16
+ end
17
+ end
@@ -36,6 +36,13 @@ module Waylon
36
36
  chain :to do |method_name|
37
37
  @method_name = method_name
38
38
  end
39
+
40
+ description do
41
+ result = "route \"#{expected}\""
42
+ result += " to action \"#{@method_name}\"" if @method_name
43
+ result += " while a member of \"#{@group}\"" if @group
44
+ result
45
+ end
39
46
  end
40
47
  end
41
48
  end
@@ -63,7 +63,7 @@ module Waylon
63
63
  msg_details[:channel_id] = channel ? channel.id : chatroom.id
64
64
  end
65
65
 
66
- TestSense.process(msg_details)
66
+ TestSense.perform(msg_details)
67
67
  TestWorker.handle(TestSense.fake_queue)
68
68
  end
69
69
  end
@@ -51,6 +51,8 @@ module Waylon
51
51
  details[:text]
52
52
  end
53
53
 
54
+ alias body text
55
+
54
56
  # Lazily provides the details for TestMessages
55
57
  # @api private
56
58
  # @return [Hash] The details for this TestMessage instance
@@ -22,14 +22,14 @@ module Waylon
22
22
  end
23
23
 
24
24
  # Overrides the Sense.enqueue class method to avoid Resque
25
- def self.enqueue(route, request_id, body)
25
+ def self.enqueue(route, request)
26
26
  details = {
27
27
  "sense" => self,
28
- "message" => request_id,
29
- "tokens" => route.tokens(body.strip)
28
+ "request" => request,
29
+ "route" => route.name
30
30
  }
31
31
 
32
- fake_queue.push [route.destination, route.action, details]
32
+ fake_queue.push [route.destination, details]
33
33
  end
34
34
 
35
35
  # Allows access to the fake version of Resque
@@ -50,15 +50,10 @@ module Waylon
50
50
  @message_list ||= []
51
51
  end
52
52
 
53
- # Receives incoming message details and places work on a queue to be performed by a Skill
54
- # @param message_details [Hash] The details necessary for creating a TestMessage
55
- # @return [void]
56
- def self.process(message_details)
57
- message_list << message_details
58
- message_id = message_list.size - 1
59
- msg = message_class.new(message_id)
60
- route = SkillRegistry.instance.route(msg) || SkillRegistry.instance.default_route
61
- enqueue(route, msg.id, msg.text)
53
+ # Provides a way to use an initial request to reconstitute a Sense-specific Message
54
+ # @return [Waylon::Message]
55
+ def self.message_from_request(request)
56
+ message_class.new(message_list.size - 1, request)
62
57
  end
63
58
 
64
59
  # Emulates reactions by sending a message with the reaction type
@@ -66,7 +61,7 @@ module Waylon
66
61
  # @param type [Symbol,String] The type of reaction to send
67
62
  # @return [void]
68
63
  def self.react(request, type)
69
- msg = message_class.new(request)
64
+ msg = message_from_request(request)
70
65
  msg.channel.post_message(":#{type}:")
71
66
  end
72
67
 
@@ -81,10 +76,21 @@ module Waylon
81
76
  # @param text [String] The message content to send in response to the request
82
77
  # @return [void]
83
78
  def self.reply(request, text)
84
- msg = message_class.new(request)
79
+ msg = message_from_request(request)
85
80
  msg.channel.post_message(text)
86
81
  end
87
82
 
83
+ # Receives incoming message details and places work on a queue to be performed by a Skill
84
+ # @param message_details [Hash] The details necessary for creating a TestMessage
85
+ # @return [void]
86
+ def self.run(message_details)
87
+ message_list << message_details
88
+ message_id = message_list.size - 1
89
+ msg = message_class.new(message_id)
90
+ route = SkillRegistry.route(msg) || SkillRegistry.instance.default_route(msg)
91
+ enqueue(route, message_details)
92
+ end
93
+
88
94
  # Provides all message text sent _by_ Waylon
89
95
  # @return [Array<String>]
90
96
  def self.sent_messages
@@ -7,8 +7,8 @@ module Waylon
7
7
  # Instructs the worker to grab an item off the Queue and run it
8
8
  # @param queue [Queue] The queue that contains work to be done
9
9
  def self.handle(queue)
10
- skill, action, details = queue.pop
11
- skill.perform(action, details)
10
+ skill, details = queue.pop
11
+ skill.perform(details)
12
12
  end
13
13
  end
14
14
  end
data/lib/waylon/sense.rb CHANGED
@@ -20,17 +20,16 @@ module Waylon
20
20
 
21
21
  # The connection between Senses and Skills happens here, via a Route and a Hash of details
22
22
  # @param route [Route] route The matching Route from the SkillRegistry
23
- # @param request_id [String] The ID (from the messaging platform) of the request
24
- # @param body [String] Message content for the Skill
23
+ # @param request [String] The request message (or its ID) from the messaging platform
25
24
  # @api private
26
- def self.enqueue(route, request_id, body)
25
+ def self.enqueue(route, request)
27
26
  details = {
28
27
  "sense" => self,
29
- "message" => request_id,
30
- "tokens" => route.tokens(body.strip)
28
+ "request" => request,
29
+ "route" => route.name
31
30
  }
32
31
 
33
- Resque.enqueue route.destination, route.action, details
32
+ Resque.enqueue route.destination, details
34
33
  end
35
34
 
36
35
  # Provides a simple mechanism for referencing the Group subclass provided by this Sense
@@ -5,6 +5,8 @@ module Waylon
5
5
  class SenseRegistry
6
6
  include Singleton
7
7
 
8
+ attr_reader :senses
9
+
8
10
  # A convenience wrapper around the singleton instance #register method
9
11
  # @param (see #register)
10
12
  # @return (see #register)
data/lib/waylon/skill.rb CHANGED
@@ -5,7 +5,7 @@ module Waylon
5
5
  class Skill
6
6
  include BaseComponent
7
7
 
8
- attr_reader :sense, :tokens, :request
8
+ attr_reader :sense, :tokens, :request, :route
9
9
 
10
10
  # Config namespace for config keys
11
11
  # @return [String] The namespace for config keys
@@ -14,32 +14,31 @@ module Waylon
14
14
  end
15
15
 
16
16
  # Resque uses this to execute the Skill. Just defers to the `action` subclass method
17
- # @param action [Symbol,String] The method on the Skill subclass to call
18
17
  # @param details [Hash] Input details about the message for running a Skill action
19
- def self.perform(action, details)
20
- new(details["sense"], details["tokens"], details["message"], details["meta"])
21
- .send(action.to_sym)
18
+ def self.perform(details)
19
+ skill = new(details["sense"], details["route"], details["request"], details["meta"])
20
+ skill.send(skill.route.action.to_sym)
22
21
  end
23
22
 
24
23
  # Redis/Resque queue name
25
24
  # @api private
26
25
  # @return [Symbol]
27
26
  def self.queue
28
- :senses
27
+ :skills
29
28
  end
30
29
 
31
30
  # Adds skills to the SkillRegistry
32
31
  # @param condition [Condition,Regexp] The condition that determines if this route applies
33
32
  # @param action [Symbol,String] The method on the Skill subclass to call
34
33
  # @param allowed_groups [Symbol,Array<Symbol>] The group or list of groups allowed to use this
35
- # @param help [String] A description of how to use the skill
36
- def self.route(condition, action, allowed_groups: :everyone, help: nil, name: nil)
34
+ # @param help [String,Hash] A description of how to use the skill
35
+ def self.route(condition, action, allowed_groups: :everyone, help: nil, name: nil, mention_only: true)
37
36
  name ||= "#{to_s.split("::").last.downcase}##{action}"
38
37
  real_cond = case condition
39
38
  when Condition
40
39
  condition
41
40
  when Regexp
42
- Conditions::Regex.new(condition, action, allowed_groups, help)
41
+ Conditions::Regex.new(condition, action, allowed_groups, help, mention_only)
43
42
  else
44
43
  log("Unknown condition for route for #{name}##{action}", :warn)
45
44
  nil
@@ -48,13 +47,13 @@ module Waylon
48
47
  end
49
48
 
50
49
  # @param sense [Class,String] Class (or Class name) of the source Sense
51
- # @param tokens [Array<String>] Tokenized message content for use in the Skill
52
- # @param request [String,Integer] Reference to the request from the Sense provider (usually an ID)
50
+ # @param request [String,Integer] Reference to the request from the Sense provider (usually ID or message itself)
53
51
  # @param meta [Hash] Optional meta data that can be passed along from a Sense for use in Skills
54
- def initialize(sense, tokens, request, meta)
52
+ def initialize(sense, route, request, meta)
55
53
  @sense = sense.is_a?(Class) ? sense : Module.const_get(sense)
56
- @tokens = tokens || []
54
+ @route = SkillRegistry.find_by_name(route)
57
55
  @request = request
56
+ @tokens = @route.tokens(message.body) || []
58
57
  @meta = meta
59
58
  end
60
59
 
@@ -62,7 +61,6 @@ module Waylon
62
61
  # @return [String]
63
62
  def acknowledgement
64
63
  responses = [
65
- "I'll get back to you in just a sec.",
66
64
  "You got it!",
67
65
  "As you wish.",
68
66
  "Certainly!",
@@ -73,11 +71,10 @@ module Waylon
73
71
  "Of course!",
74
72
  "I'd be delighted!",
75
73
  "Right away!",
76
- "Gladly",
74
+ "Gladly!",
77
75
  "All right.",
78
76
  "I'm all over it.",
79
77
  "I'm on it!",
80
- "Let me see what I can do.",
81
78
  "Will do!"
82
79
  ]
83
80
  responses.sample
@@ -97,7 +94,8 @@ module Waylon
97
94
  sense: @sense,
98
95
  message: @request,
99
96
  tokens: @tokens,
100
- meta: @meta
97
+ meta: @meta,
98
+ route: route.name
101
99
  }
102
100
  end
103
101
 
@@ -111,7 +109,11 @@ module Waylon
111
109
  # Provides a wrapped message for responding to the received Sense
112
110
  # @return [Waylon::Message]
113
111
  def message
114
- sense.message_class.new(request)
112
+ sense.message_from_request(request)
113
+ end
114
+
115
+ def named_tokens
116
+ @named_tokens ||= @route.named_tokens(message.body) || {}
115
117
  end
116
118
 
117
119
  # Defers to the Sense to react to a message
@@ -124,9 +126,40 @@ module Waylon
124
126
  end
125
127
 
126
128
  # Defers to the Sense to determine how to reply to a message
127
- # @param [String] text The reply text
128
- def reply(text)
129
- sense.reply(request, text)
129
+ # @param text [String] The reply text
130
+ def reply(text, private: false)
131
+ if private && sense.supports?(:private_messages)
132
+ sense.private_reply(request, text)
133
+ else
134
+ log("Unable to send private message for Sense #{sense.name}, replying instead", :debug) if private
135
+ sense.reply(request, text)
136
+ end
137
+ end
138
+
139
+ # Defers to the Sense to determine how to reply to a message with rich content
140
+ # @param blocks [String] The reply blocks
141
+ def reply_with_blocks(blocks, private: false)
142
+ unless sense.supports?(:blocks)
143
+ log("Unable to use blocks with Sense #{sense.name}")
144
+ return false
145
+ end
146
+ if private
147
+ sense.private_reply_with_blocks(request, blocks)
148
+ else
149
+ log("Unable to send private message for Sense #{sense.name}, replying instead", :debug) if private
150
+ sense.reply_with_blocks(request, blocks)
151
+ end
152
+ end
153
+
154
+ # Defers to the Sense to do threaded replies if it can, otherwise it falls back to normal replies
155
+ # @param text [String] The reply text
156
+ def threaded_reply(text)
157
+ if sense.supports?(:threads)
158
+ sense.threaded_reply(request, text)
159
+ else
160
+ log("Unable to reply in theads for Sense #{sense.name}, replying instead", :debug)
161
+ sense.reply(request, text)
162
+ end
130
163
  end
131
164
  end
132
165
  end