waylon-core 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -1
- data/.ruby-version +1 -1
- data/Gemfile.lock +9 -9
- data/lib/waylon/condition.rb +26 -1
- data/lib/waylon/conditions/black_hole.rb +34 -0
- data/lib/waylon/conditions/regex.rb +5 -0
- data/lib/waylon/config.rb +3 -2
- data/lib/waylon/core.rb +5 -0
- data/lib/waylon/group.rb +1 -1
- data/lib/waylon/logger.rb +1 -1
- data/lib/waylon/message.rb +19 -0
- data/lib/waylon/route.rb +1 -1
- data/lib/waylon/routes/black_hole.rb +17 -0
- data/lib/waylon/rspec/matchers/route_matcher.rb +7 -0
- data/lib/waylon/rspec/skill.rb +1 -1
- data/lib/waylon/rspec/test_message.rb +2 -0
- data/lib/waylon/rspec/test_sense.rb +21 -15
- data/lib/waylon/rspec/test_worker.rb +2 -2
- data/lib/waylon/sense.rb +5 -6
- data/lib/waylon/sense_registry.rb +2 -0
- data/lib/waylon/skill.rb +54 -21
- data/lib/waylon/skill_registry.rb +47 -10
- data/lib/waylon/skills/default.rb +5 -0
- data/lib/waylon/skills/diagnostics.rb +58 -0
- data/lib/waylon/skills/fun.rb +5 -2
- data/lib/waylon/skills/groups.rb +167 -0
- data/lib/waylon/skills/help.rb +174 -0
- data/lib/waylon/user.rb +5 -0
- data/lib/waylon/version.rb +1 -1
- data/lib/waylon/webhook.rb +24 -25
- data/lib/waylon.rb +3 -0
- data/waylon-core.gemspec +1 -1
- metadata +10 -5
@@ -5,6 +5,8 @@ module Waylon
|
|
5
5
|
class SkillRegistry
|
6
6
|
include Singleton
|
7
7
|
|
8
|
+
attr_reader :routes
|
9
|
+
|
8
10
|
# A wrapper around the singleton #register method
|
9
11
|
# @param name [String] The name of the skill in the registry
|
10
12
|
# @param class_name [Class] The class to associate with the name
|
@@ -14,8 +16,24 @@ module Waylon
|
|
14
16
|
instance.register(name, class_name, condition)
|
15
17
|
end
|
16
18
|
|
17
|
-
def
|
18
|
-
|
19
|
+
def self.find_by_name(name)
|
20
|
+
[
|
21
|
+
*instance.routes,
|
22
|
+
Routes::PermissionDenied.new,
|
23
|
+
Routes::BlackHole.new,
|
24
|
+
Routes::Default.new
|
25
|
+
].find { |r| r.name == name.to_s }
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.route(message)
|
29
|
+
instance.route(message)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Provides the default route based on the received message.
|
33
|
+
# @param message [Waylon::Message] The received message
|
34
|
+
# @return [Waylon::Route]
|
35
|
+
def default_route(message)
|
36
|
+
message.to_bot? ? Routes::Default.new : Routes::BlackHole.new
|
19
37
|
end
|
20
38
|
|
21
39
|
# Gathers a Hash of help data for all routes a user is permitted to access
|
@@ -23,9 +41,13 @@ module Waylon
|
|
23
41
|
# @return [Hash]
|
24
42
|
def help(user)
|
25
43
|
data = {}
|
26
|
-
@routes.select { |r| r.permits?(user) }.each do |permitted|
|
27
|
-
data[permitted.destination.
|
28
|
-
data[permitted.destination.
|
44
|
+
@routes.select { |r| r.permits?(user) && r.mention_only? }.each do |permitted|
|
45
|
+
data[permitted.destination.component_namespace] ||= []
|
46
|
+
data[permitted.destination.component_namespace] << if permitted.help
|
47
|
+
{ name: permitted.name, help: permitted.help }
|
48
|
+
else
|
49
|
+
{ name: permitted.name }
|
50
|
+
end
|
29
51
|
end
|
30
52
|
|
31
53
|
data.reject { |_k, v| v.empty? }
|
@@ -53,14 +75,25 @@ module Waylon
|
|
53
75
|
# Given a message, find a suitable skill Route for it (sorted by priority, highest first)
|
54
76
|
# @param message [Waylon::Message] A Message instance
|
55
77
|
# @return [Hash]
|
78
|
+
# rubocop:disable Metrics/AbcSize
|
79
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
80
|
+
# rubocop:disable Metrics/MethodLength
|
81
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
56
82
|
def route(message)
|
57
83
|
route = nil
|
58
|
-
message_text = message.
|
84
|
+
message_text = message.body.strip
|
59
85
|
@routes ||= []
|
60
|
-
@routes.sort_by(&:priority).reverse.each do |
|
61
|
-
if
|
62
|
-
|
63
|
-
|
86
|
+
@routes.sort_by(&:priority).reverse.each do |this_route|
|
87
|
+
if this_route.permits?(message.author) &&
|
88
|
+
this_route.matches?(message_text) &&
|
89
|
+
(this_route.properly_mentions?(message) || message.private?)
|
90
|
+
route = this_route
|
91
|
+
elsif this_route.permits?(message.author) &&
|
92
|
+
this_route.matches?(message_text) &&
|
93
|
+
!this_route.properly_mentions?(message)
|
94
|
+
# Black hole these because they're not direct mentions
|
95
|
+
route = Routes::BlackHole.new
|
96
|
+
elsif this_route.matches?(message_text)
|
64
97
|
route = Routes::PermissionDenied.new
|
65
98
|
end
|
66
99
|
if route
|
@@ -70,5 +103,9 @@ module Waylon
|
|
70
103
|
end
|
71
104
|
route
|
72
105
|
end
|
106
|
+
# rubocop:enable Metrics/AbcSize
|
107
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
108
|
+
# rubocop:enable Metrics/MethodLength
|
109
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
73
110
|
end
|
74
111
|
end
|
@@ -20,6 +20,11 @@ module Waylon
|
|
20
20
|
reply("#{prefix} #{responses.sample} #{help_postfix}")
|
21
21
|
end
|
22
22
|
|
23
|
+
# This action ignores messages
|
24
|
+
def ignore
|
25
|
+
log("Ignoring black-holed message from #{message.author.email}")
|
26
|
+
end
|
27
|
+
|
23
28
|
# A useful addition to message to tell the User how to get help
|
24
29
|
# @return [String]
|
25
30
|
def help_postfix
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Waylon
|
4
|
+
module Skills
|
5
|
+
# Built-in info routes
|
6
|
+
class Diagnostics < Skill
|
7
|
+
# Say hello to Waylon
|
8
|
+
route(
|
9
|
+
/^diagnostics|status$/i,
|
10
|
+
:status,
|
11
|
+
help: {
|
12
|
+
usage: "diagnostics|status",
|
13
|
+
description: "Retrieve this bot's current status"
|
14
|
+
}
|
15
|
+
)
|
16
|
+
|
17
|
+
# Provides info about Waylon's status
|
18
|
+
def status # rubocop:disable Metrics/AbcSize
|
19
|
+
response = []
|
20
|
+
response << "*Framework Version:* Waylon v#{Waylon::Core::VERSION}"
|
21
|
+
response << "*Sense plugins:*"
|
22
|
+
loaded_senses.each { |c| response << " - #{c}" }
|
23
|
+
response << "*Skill plugins:*"
|
24
|
+
loaded_routes.each { |d| response << " - #{d}" }
|
25
|
+
response << "*Redis:*"
|
26
|
+
state, read_time, write_time = test_redis
|
27
|
+
response << " - *Test Result:* #{state ? "Success" : "Error"}"
|
28
|
+
response << " - *Read time:* #{read_time}s"
|
29
|
+
response << " - *Write time:* #{write_time}s"
|
30
|
+
response << "*Queue Monitoring:*"
|
31
|
+
response << " - Failed jobs: #{Resque::Failure.count}"
|
32
|
+
|
33
|
+
reply response.join("\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
def loaded_routes
|
37
|
+
SkillRegistry.instance.routes.map { |r| r.destination.name }.sort.uniq
|
38
|
+
end
|
39
|
+
|
40
|
+
def loaded_senses
|
41
|
+
SenseRegistry.instance.senses.map { |_s, c| c.name }.sort.uniq
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_redis
|
45
|
+
test_key = ("a".."z").to_a.sample(10).join
|
46
|
+
test_value = (0..1000).to_a.sample(20).map(&:to_s).join
|
47
|
+
test_result = nil
|
48
|
+
|
49
|
+
write_time = Benchmark.realtime { db.store(test_key, test_value) }
|
50
|
+
read_time = Benchmark.realtime { test_result = db.load(test_key) }
|
51
|
+
|
52
|
+
db.delete(test_key)
|
53
|
+
|
54
|
+
[(test_value == test_result), read_time.round(6), write_time.round(6)]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/waylon/skills/fun.rb
CHANGED
@@ -6,8 +6,9 @@ module Waylon
|
|
6
6
|
class Fun < Skill
|
7
7
|
# Say hello to Waylon
|
8
8
|
route(
|
9
|
-
/^(hello|hi)
|
10
|
-
:hello
|
9
|
+
/^(hello|hi)([.!]+)?$/i,
|
10
|
+
:hello,
|
11
|
+
help: "hi|hello"
|
11
12
|
)
|
12
13
|
|
13
14
|
# Responds to "hello" in less boring ways
|
@@ -19,6 +20,8 @@ module Waylon
|
|
19
20
|
"How can I be of service?"
|
20
21
|
]
|
21
22
|
|
23
|
+
react :wave
|
24
|
+
|
22
25
|
reply responses.sample
|
23
26
|
end
|
24
27
|
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Waylon
|
4
|
+
module Skills
|
5
|
+
# Built-in skills for managing groups
|
6
|
+
class Groups < Skill
|
7
|
+
route(
|
8
|
+
/^add (.+) to (.+)$/,
|
9
|
+
:add_to_group,
|
10
|
+
help: {
|
11
|
+
usage: "add USER[,USER] to GROUP",
|
12
|
+
description: "Add USER(s) to a GROUP"
|
13
|
+
},
|
14
|
+
allowed_groups: %i[admins group_admins]
|
15
|
+
)
|
16
|
+
|
17
|
+
route(
|
18
|
+
/^remove (.+) from (.+)$/,
|
19
|
+
:remove_from_group,
|
20
|
+
help: {
|
21
|
+
usage: "remove USER[,USER] from GROUP",
|
22
|
+
description: "Remove USER(s) from a GROUP"
|
23
|
+
},
|
24
|
+
allowed_groups: %i[admins group_admins]
|
25
|
+
)
|
26
|
+
|
27
|
+
route(
|
28
|
+
/^(describe|list|print|show) (all )?(groups|group memberships)$/,
|
29
|
+
:list_all_groups,
|
30
|
+
help: {
|
31
|
+
usage: "list all groups",
|
32
|
+
description: "List all groups and their members"
|
33
|
+
},
|
34
|
+
allowed_groups: %i[admins group_admins]
|
35
|
+
)
|
36
|
+
|
37
|
+
route(
|
38
|
+
/^cleanup groups$/,
|
39
|
+
:cleanup_groups,
|
40
|
+
help: {
|
41
|
+
usage: "cleanup groups",
|
42
|
+
description: "Remove empty groups"
|
43
|
+
},
|
44
|
+
allowed_groups: %i[admins group_admins]
|
45
|
+
)
|
46
|
+
|
47
|
+
route(
|
48
|
+
/^((list )?my )?groups$/,
|
49
|
+
:list_my_groups,
|
50
|
+
help: {
|
51
|
+
usage: "list my groups",
|
52
|
+
description: "List my group memberships"
|
53
|
+
}
|
54
|
+
)
|
55
|
+
|
56
|
+
def add_to_group # rubocop:disable Metrics/AbcSize
|
57
|
+
user_list = tokens.first
|
58
|
+
group_name = tokens.last
|
59
|
+
|
60
|
+
if group_name == "global admins"
|
61
|
+
reply "Sorry, I can't manipulate global admins this way..."
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
group = Group.new(group_name)
|
66
|
+
|
67
|
+
log "Adding #{user_list} to group #{group}"
|
68
|
+
|
69
|
+
failures = []
|
70
|
+
user_list.split(",").each do |this_user|
|
71
|
+
found = found_user(this_user)
|
72
|
+
failures << found unless group.add(found)
|
73
|
+
end
|
74
|
+
|
75
|
+
unless failures.empty?
|
76
|
+
text = failures.size > 1 ? "were already members" : "was already a member"
|
77
|
+
reply "Looks like [#{failures.map { |u| mention(u) }.join(", ")}] #{text} of #{group_name}"
|
78
|
+
end
|
79
|
+
|
80
|
+
reply("Done adding users to #{group_name}!")
|
81
|
+
end
|
82
|
+
|
83
|
+
def cleanup_groups
|
84
|
+
# perform a key scan in Redis for all group keys and find empty groups
|
85
|
+
group_keys = all_group_keys.select do |group|
|
86
|
+
name = group.split(".").last
|
87
|
+
Group.new(name).to_a.empty?
|
88
|
+
end
|
89
|
+
|
90
|
+
# delete the empty group keys
|
91
|
+
group_keys.each { |g| db.delete(g) }
|
92
|
+
|
93
|
+
group_names = group_keys.map { |g| g.split(".").last }
|
94
|
+
|
95
|
+
reply "I removed these empty groups: #{group_names.join(", ")}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def remove_from_group # rubocop:disable Metrics/AbcSize
|
99
|
+
user_list = tokens.first
|
100
|
+
group_name = tokens.last
|
101
|
+
|
102
|
+
if group_name == "global admins"
|
103
|
+
reply "Sorry, I can't manipulate global admins this way..."
|
104
|
+
return
|
105
|
+
end
|
106
|
+
|
107
|
+
group = Group.new(group_name)
|
108
|
+
|
109
|
+
log "Removing #{user_list} from group '#{group_name}'", :debug
|
110
|
+
|
111
|
+
failures = []
|
112
|
+
user_list.split(",").each do |this_user|
|
113
|
+
found = found_user(this_user)
|
114
|
+
failures << found unless group.remove(found)
|
115
|
+
end
|
116
|
+
|
117
|
+
unless failures.empty?
|
118
|
+
text = failures.size > 1 ? "were members" : "was a member"
|
119
|
+
reply("I don't think [#{failures.map { |u| mention(u) }.join(", ")}] #{text} of #{group_name}")
|
120
|
+
end
|
121
|
+
reply("Done removing users from groups!")
|
122
|
+
end
|
123
|
+
|
124
|
+
def list_all_groups
|
125
|
+
groups = {}
|
126
|
+
groups["global admins"] = global_admins unless global_admins.empty?
|
127
|
+
all_group_keys.each do |group|
|
128
|
+
name = group.split(".").last
|
129
|
+
groups[name] = Group.new(name).members
|
130
|
+
end
|
131
|
+
|
132
|
+
reply(codify(groups.to_yaml))
|
133
|
+
end
|
134
|
+
|
135
|
+
def list_my_groups
|
136
|
+
groups = []
|
137
|
+
groups << "global admins" if global_admins.include?(message.author.email)
|
138
|
+
all_group_keys.each do |group|
|
139
|
+
name = group.split(".").last
|
140
|
+
groups << name if Group.new(name).include?(message.author)
|
141
|
+
end
|
142
|
+
|
143
|
+
reply(codify(groups.to_yaml))
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def all_group_keys
|
149
|
+
db.adapter.backend.keys("groups.*")
|
150
|
+
end
|
151
|
+
|
152
|
+
def found_user(user_string)
|
153
|
+
if user_string =~ /^.+@.+/
|
154
|
+
# If provided an email
|
155
|
+
sense.user_class.find_by_email(user_string)
|
156
|
+
else
|
157
|
+
# Otherwise assume we're provided their user ID
|
158
|
+
sense.user_class.from_mention(user_string)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def global_admins
|
163
|
+
Config.instance.admins
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Waylon
|
4
|
+
module Skills
|
5
|
+
# A Skill for providing help
|
6
|
+
class Help < Skill
|
7
|
+
# Ask for some help.
|
8
|
+
# Defaults to all things a user has access to, but allows specifying either a Skill name or a Skill and Route
|
9
|
+
route(
|
10
|
+
/^help(?<skill_clause>\s+(?<skill>\w+)(?<action_clause>#(?<action>\w+)|\s+(?<action>\w+))?)?$/i,
|
11
|
+
:help,
|
12
|
+
help: {
|
13
|
+
usage: "help [skill [action]]",
|
14
|
+
description: "Allows asking for help, either for all skills or for a particular skill or action"
|
15
|
+
}
|
16
|
+
)
|
17
|
+
|
18
|
+
# Responds to "help" requests
|
19
|
+
def help
|
20
|
+
skill = named_tokens[:skill]
|
21
|
+
action = named_tokens[:action]
|
22
|
+
|
23
|
+
react :book
|
24
|
+
|
25
|
+
immediate_responses = [
|
26
|
+
"I'll send you a DM to go over that with you.",
|
27
|
+
"I'll DM you the details.",
|
28
|
+
"Look for a private message with those details.",
|
29
|
+
"You should have a private message with that information shortly."
|
30
|
+
]
|
31
|
+
|
32
|
+
# Only send this if you aren't already in a DM
|
33
|
+
threaded_reply "#{acknowledgement} #{immediate_responses.sample}" unless message.private?
|
34
|
+
|
35
|
+
if sense.supports?(:blocks)
|
36
|
+
reply_with_blocks(help_blocks(skill, action), private: true)
|
37
|
+
else
|
38
|
+
reply(help_text(skill, action), private: true)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def help_text(skill = nil, action = nil) # rubocop:disable Metrics/AbcSize
|
43
|
+
allowed_routes = SkillRegistry.instance.help(message.author)
|
44
|
+
resp = []
|
45
|
+
if skill
|
46
|
+
if action
|
47
|
+
resp << "## Help for #{skill}##{action}:"
|
48
|
+
this_route = allowed_routes[skill].find do |r|
|
49
|
+
r[:name].to_s == "#{skill}##{action}"
|
50
|
+
end
|
51
|
+
return "I couldn't find #{action} on #{skill}..." unless this_route
|
52
|
+
|
53
|
+
resp << build_help_text(this_route)
|
54
|
+
else
|
55
|
+
resp << "## Help for #{skill}:\n"
|
56
|
+
routes = allowed_routes[skill]
|
57
|
+
(routes || []).each { |r| resp << build_help_text(r) }
|
58
|
+
end
|
59
|
+
else
|
60
|
+
help_text_for_all(allowed_routes)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def help_blocks(skill = nil, action = nil) # rubocop:disable Metrics/AbcSize
|
65
|
+
allowed_routes = SkillRegistry.instance.help(message.author)
|
66
|
+
if skill
|
67
|
+
if action
|
68
|
+
this_route = allowed_routes[skill].find do |r|
|
69
|
+
r[:name].to_s == "#{skill}##{action}"
|
70
|
+
end
|
71
|
+
|
72
|
+
return not_found_block(skill, action) unless this_route
|
73
|
+
|
74
|
+
[build_header_block(skill, action), build_help_block(this_route)]
|
75
|
+
else
|
76
|
+
routes = allowed_routes[skill]
|
77
|
+
return not_found_block(skill, nil) unless routes
|
78
|
+
|
79
|
+
resp = [build_header_block(skill, nil)]
|
80
|
+
routes.each { |r| resp << build_help_block(r) }
|
81
|
+
resp
|
82
|
+
end
|
83
|
+
else
|
84
|
+
allowed_routes = SkillRegistry.instance.help(message.author)
|
85
|
+
help_blocks_for_all(allowed_routes)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def help_blocks_for_all(routes)
|
90
|
+
resp = [
|
91
|
+
{ type: "header", text: { type: "plain_text", text: "All known actions:", emoji: true } }
|
92
|
+
]
|
93
|
+
|
94
|
+
routes.each do |k, v|
|
95
|
+
resp += [
|
96
|
+
{ type: "divider" },
|
97
|
+
{
|
98
|
+
type: "section",
|
99
|
+
text: {
|
100
|
+
type: "mrkdwn",
|
101
|
+
text: "*Actions for '#{k}':*"
|
102
|
+
}
|
103
|
+
}
|
104
|
+
]
|
105
|
+
v.each { |r| resp << build_help_block(r) }
|
106
|
+
end
|
107
|
+
resp
|
108
|
+
end
|
109
|
+
|
110
|
+
def help_text_for_all(routes)
|
111
|
+
resp = []
|
112
|
+
resp << "*All known actions:*\n"
|
113
|
+
routes.each do |k, v|
|
114
|
+
resp << "* *#{k}*:"
|
115
|
+
v.each do |r|
|
116
|
+
resp << build_help_text(r)
|
117
|
+
end
|
118
|
+
resp << " --- "
|
119
|
+
end
|
120
|
+
resp.join("\n")
|
121
|
+
end
|
122
|
+
|
123
|
+
def build_header_block(skill, action)
|
124
|
+
text = if action
|
125
|
+
"Help for #{skill}##{action}:"
|
126
|
+
else
|
127
|
+
"Help for #{skill}:"
|
128
|
+
end
|
129
|
+
{ type: "header", text: { type: "plain_text", text: text, emoji: true } }
|
130
|
+
end
|
131
|
+
|
132
|
+
def build_help_block(this_route)
|
133
|
+
{
|
134
|
+
type: "section",
|
135
|
+
text: {
|
136
|
+
type: "mrkdwn",
|
137
|
+
text: " *#{this_route[:name]}*\n#{build_help_text(this_route)}"
|
138
|
+
}
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_help_text(this_route)
|
143
|
+
route_help_text = []
|
144
|
+
case this_route[:help]
|
145
|
+
when String
|
146
|
+
route_help_text << " *Usage:* #{this_route[:help]}"
|
147
|
+
when Hash
|
148
|
+
route_help_text << " *Usage:* #{this_route.dig(:help, :usage)}"
|
149
|
+
if this_route.dig(:help, :description)
|
150
|
+
route_help_text << "\n *Description:* #{this_route.dig(:help, :description)}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
route_help_text.join
|
154
|
+
end
|
155
|
+
|
156
|
+
def not_found_block(skill, action)
|
157
|
+
sentence = if action
|
158
|
+
"I couldn't find any '#{action}' action on the '#{skill}' skill..."
|
159
|
+
else
|
160
|
+
"I couldn't find any routes related to a '#{skill}' skill..."
|
161
|
+
end
|
162
|
+
[
|
163
|
+
{
|
164
|
+
type: "section",
|
165
|
+
text: {
|
166
|
+
type: "mrkdwn",
|
167
|
+
text: sentence
|
168
|
+
}
|
169
|
+
}
|
170
|
+
]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
data/lib/waylon/user.rb
CHANGED
@@ -18,6 +18,11 @@ module Waylon
|
|
18
18
|
raise NotImplementedError, "find_by_handle isn't implemented"
|
19
19
|
end
|
20
20
|
|
21
|
+
# This should be overridden by subclasses to provide a mechanism for looking up Users based on mention strings
|
22
|
+
def from_mention(_mention_string)
|
23
|
+
raise NotImplementedError, "from_mention isn't implemented"
|
24
|
+
end
|
25
|
+
|
21
26
|
# Provides a simple mechanism for referencing User subclass's Sense
|
22
27
|
# @return [Class] A Sense subclass
|
23
28
|
def sense
|
data/lib/waylon/version.rb
CHANGED
data/lib/waylon/webhook.rb
CHANGED
@@ -21,7 +21,7 @@ module Waylon
|
|
21
21
|
# @return [Class] The name of the corresponding Sense class
|
22
22
|
def sense_class
|
23
23
|
last = self.class.name.split("::").last
|
24
|
-
Module.const_get("Senses::#{last}")
|
24
|
+
Module.const_get("Waylon::Senses::#{last}")
|
25
25
|
end
|
26
26
|
|
27
27
|
# This must be implemented on every Webhook to provide a mechanism to ensure received payloads are legit
|
@@ -38,36 +38,35 @@ module Waylon
|
|
38
38
|
end
|
39
39
|
|
40
40
|
before do
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
unless request.get? || request.options?
|
45
|
-
request.body.rewind
|
46
|
-
@parsed_body = JSON.parse(request.body.read, symbolize_names: true)
|
47
|
-
end
|
48
|
-
rescue StandardError => e
|
49
|
-
halt(400, { error: "Request must be JSON: #{e.message}" }.to_json)
|
41
|
+
unless request.get? || request.options?
|
42
|
+
request.body.rewind
|
43
|
+
@parsed_body = JSON.parse(request.body.read, symbolize_names: true)
|
50
44
|
end
|
45
|
+
rescue StandardError => e
|
46
|
+
content_type "application/json"
|
47
|
+
halt(400, { error: "Request must be JSON: #{e.message}" }.to_json)
|
51
48
|
end
|
52
49
|
|
53
50
|
after do
|
54
51
|
request.options? && headers("Access-Control-Allow-Methods" => @allowed_types || %w[OPTIONS POST])
|
55
52
|
end
|
56
53
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
54
|
+
## Example incoming webhook
|
55
|
+
#
|
56
|
+
# post "/" do
|
57
|
+
# begin
|
58
|
+
# request.body.rewind
|
59
|
+
# verify request.body.read, request.env
|
60
|
+
# enqueue @parsed_body
|
61
|
+
# rescue StandardError => e
|
62
|
+
# halt(422, { error: "Unprocessable entity: #{e.message}" }.to_json)
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# { status: :ok }.to_json
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# options "/" do
|
69
|
+
# halt 200
|
70
|
+
# end
|
72
71
|
end
|
73
72
|
end
|
data/lib/waylon.rb
CHANGED
data/waylon-core.gemspec
CHANGED
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.add_dependency "puma", "~> 5.5"
|
36
36
|
spec.add_dependency "resque", "~> 2.2"
|
37
37
|
|
38
|
-
spec.add_development_dependency "bundler", "~> 2.
|
38
|
+
spec.add_development_dependency "bundler", "~> 2.3"
|
39
39
|
spec.add_development_dependency "rake", "~> 13.0"
|
40
40
|
spec.add_development_dependency "rspec", "~> 3.10"
|
41
41
|
spec.add_development_dependency "rubocop", "~> 1.23"
|