lita-conferenz 1.0.1
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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +3 -0
- data/Rakefile +6 -0
- data/lib/lita-conferenz.rb +11 -0
- data/lib/lita/handlers/conferenz/chat.rb +205 -0
- data/lib/lita/handlers/conferenz/config.rb +20 -0
- data/lib/lita/handlers/conferenz/term.rb +153 -0
- data/lita-conferenz.gemspec +24 -0
- data/locales/en.yml +40 -0
- data/spec/lita/handlers/conferenz/chat_spec.rb +339 -0
- data/spec/spec_helper.rb +12 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b4b5b603523ab6945ca312403138be66d58bb927
|
4
|
+
data.tar.gz: 94583855cb7b7a9a8545ece100e7db0b8202f8bf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6e3089bc86479ea11ee2da7a1b1ba80594883bd9b443481ae74d6cb482426f30b6d6741cf4d788b0c19672b4851bb98c1d069858e440666953a0b54e99ba2454
|
7
|
+
data.tar.gz: b54af757417eff2d669d94fe99b6b4e2092375d9c78a3bcb69a801c4ec7d2cf94eac855d5ed1cd6d76356dd63c0b2776968c071e039a814d6d3951f14eb5c355
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
require "lita"
|
4
|
+
|
5
|
+
Lita.load_locales Dir[File.expand_path(
|
6
|
+
File.join("..", "..", "locales", "*.yml"), __FILE__
|
7
|
+
)]
|
8
|
+
|
9
|
+
require "lita/handlers/conferenz/chat"
|
10
|
+
require 'lita/handlers/conferenz/config'
|
11
|
+
require 'lita/handlers/conferenz/term'
|
@@ -0,0 +1,205 @@
|
|
1
|
+
module Lita::Handlers::Conferenz
|
2
|
+
class Chat < Lita::Handler
|
3
|
+
namespace "conferenz"
|
4
|
+
|
5
|
+
on :loaded, :define_routes
|
6
|
+
|
7
|
+
def define_routes(payload)
|
8
|
+
define_static_routes
|
9
|
+
define_dynamic_routes(config.term_pattern.source)
|
10
|
+
end
|
11
|
+
|
12
|
+
def increment(response)
|
13
|
+
modify(response, :increment)
|
14
|
+
end
|
15
|
+
|
16
|
+
def decrement(response)
|
17
|
+
modify(response, :decrement)
|
18
|
+
end
|
19
|
+
|
20
|
+
def check(response)
|
21
|
+
seen = Set.new
|
22
|
+
|
23
|
+
output = response.matches.map do |match|
|
24
|
+
term = get_term(match[0])
|
25
|
+
next if seen.include?(term)
|
26
|
+
seen << term
|
27
|
+
term.check
|
28
|
+
end.compact
|
29
|
+
|
30
|
+
response.reply output.join("; ")
|
31
|
+
end
|
32
|
+
|
33
|
+
def list_best(response)
|
34
|
+
list(response, :list_best)
|
35
|
+
end
|
36
|
+
|
37
|
+
def list_worst(response)
|
38
|
+
list(response, :list_worst)
|
39
|
+
end
|
40
|
+
|
41
|
+
def link(response)
|
42
|
+
response.matches.each do |match|
|
43
|
+
term1 = get_term(match[0])
|
44
|
+
term2 = get_term(match[1])
|
45
|
+
|
46
|
+
result = term1.link(term2)
|
47
|
+
|
48
|
+
case result
|
49
|
+
when Integer
|
50
|
+
response.reply t("threshold_not_satisfied", threshold: result)
|
51
|
+
when true
|
52
|
+
response.reply t("link_success", source: term2, target: term1)
|
53
|
+
else
|
54
|
+
response.reply t("already_linked", source: term2, target: term1)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def unlink(response)
|
60
|
+
response.matches.each do |match|
|
61
|
+
term1 = get_term(match[0])
|
62
|
+
term2 = get_term(match[1])
|
63
|
+
|
64
|
+
if term1.unlink(term2)
|
65
|
+
response.reply t("unlink_success", source: term2, target: term1)
|
66
|
+
else
|
67
|
+
response.reply t("already_unlinked", source: term2, target: term1)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def modified(response)
|
73
|
+
term = get_term(response.args[1])
|
74
|
+
|
75
|
+
users = term.modified
|
76
|
+
|
77
|
+
if users.empty?
|
78
|
+
response.reply t("never_modified", term: term)
|
79
|
+
else
|
80
|
+
response.reply users.map(&:name).join(", ")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete(response)
|
85
|
+
term = Term.new(robot, response.message.body.sub(/^conferenz delete /, ""), normalize: false)
|
86
|
+
|
87
|
+
if term.delete
|
88
|
+
response.reply t("delete_success", term: term)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def define_dynamic_routes(pattern)
|
95
|
+
self.class.route(
|
96
|
+
%r{(#{pattern})\+\+#{token_terminator.source}},
|
97
|
+
:increment,
|
98
|
+
help: { t("help.increment_key") => t("help.increment_value") }
|
99
|
+
)
|
100
|
+
|
101
|
+
self.class.route(
|
102
|
+
%r{(#{pattern})--#{token_terminator.source}},
|
103
|
+
:decrement,
|
104
|
+
help: { t("help.decrement_key") => t("help.decrement_value") }
|
105
|
+
)
|
106
|
+
|
107
|
+
self.class.route(
|
108
|
+
%r{(#{pattern})~~#{token_terminator.source}},
|
109
|
+
:check,
|
110
|
+
help: { t("help.check_key") => t("help.check_value") }
|
111
|
+
)
|
112
|
+
|
113
|
+
self.class.route(
|
114
|
+
%r{^(#{pattern})\s*\+=\s*(#{pattern})(?:\+\+|--|~~)?#{token_terminator.source}},
|
115
|
+
:link,
|
116
|
+
command: true,
|
117
|
+
help: { t("help.link_key") => t("help.link_value") }
|
118
|
+
)
|
119
|
+
|
120
|
+
self.class.route(
|
121
|
+
%r{^(#{pattern})\s*-=\s*(#{pattern})(?:\+\+|--|~~)?#{token_terminator.source}},
|
122
|
+
:unlink,
|
123
|
+
command: true,
|
124
|
+
help: { t("help.unlink_key") => t("help.unlink_value") }
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
def define_static_routes
|
129
|
+
self.class.route(
|
130
|
+
%r{^conferenz\s+worst},
|
131
|
+
:list_worst,
|
132
|
+
command: true,
|
133
|
+
help: { t("help.list_worst_key") => t("help.list_worst_value") }
|
134
|
+
)
|
135
|
+
|
136
|
+
self.class.route(
|
137
|
+
%r{^conferenz\s+best},
|
138
|
+
:list_best,
|
139
|
+
command: true,
|
140
|
+
help: { t("help.list_best_key") => t("help.list_best_value") }
|
141
|
+
)
|
142
|
+
|
143
|
+
self.class.route(
|
144
|
+
%r{^conferenz\s+modified\s+.+},
|
145
|
+
:modified,
|
146
|
+
command: true,
|
147
|
+
help: { t("help.modified_key") => t("help.modified_value") }
|
148
|
+
)
|
149
|
+
|
150
|
+
self.class.route(
|
151
|
+
%r{^conferenz\s+delete},
|
152
|
+
:delete,
|
153
|
+
command: true,
|
154
|
+
restrict_to: :conferenz_admins,
|
155
|
+
help: { t("help.delete_key") => t("help.delete_value") }
|
156
|
+
)
|
157
|
+
|
158
|
+
self.class.route(%r{^conferenz\s*$}, :list_best, command: true)
|
159
|
+
end
|
160
|
+
|
161
|
+
def determine_list_count(response)
|
162
|
+
n = (response.args[1] || 5).to_i - 1
|
163
|
+
n = 25 if n > 25
|
164
|
+
n
|
165
|
+
end
|
166
|
+
|
167
|
+
def get_term(term)
|
168
|
+
Term.new(robot, term)
|
169
|
+
end
|
170
|
+
|
171
|
+
def list(response, method_name)
|
172
|
+
terms_and_scores = Term.public_send(method_name, robot, determine_list_count(response))
|
173
|
+
|
174
|
+
output = terms_and_scores.each_with_index.map do |term_and_score, index|
|
175
|
+
"#{index + 1}. #{term_and_score[0]} (#{term_and_score[1].to_i})"
|
176
|
+
end.join("\n")
|
177
|
+
|
178
|
+
if output.empty?
|
179
|
+
response.reply t("no_terms")
|
180
|
+
else
|
181
|
+
response.reply output
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def modify(response, method_name)
|
186
|
+
user = response.user
|
187
|
+
|
188
|
+
output = response.matches.map do |match|
|
189
|
+
get_term(match[0]).public_send(method_name, user)
|
190
|
+
end
|
191
|
+
|
192
|
+
response.reply output.join("; ")
|
193
|
+
end
|
194
|
+
|
195
|
+
# To ensure that constructs like foo++bar or foo--bar (the latter is
|
196
|
+
# common in some URL generation schemes) do not cause errant conferenz
|
197
|
+
# modifications, force conferenz tokens be followed by whitespace (in a zero-
|
198
|
+
# width, look-ahead operator) or the end of the string.
|
199
|
+
def token_terminator
|
200
|
+
%r{(?:(?=\s)|$)}
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
Lita.register_handler(Lita::Handlers::Conferenz::Chat)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Lita::Handlers::Conferenz
|
2
|
+
class Config < Lita::Handler
|
3
|
+
namespace "conferenz"
|
4
|
+
|
5
|
+
default_term_normalizer = proc do |term|
|
6
|
+
term.to_s.downcase.strip
|
7
|
+
end
|
8
|
+
|
9
|
+
config :cooldown, types: [Integer, nil], default: 0
|
10
|
+
config :link_conferenz_threshold, types: [Integer, nil], default: 0
|
11
|
+
config :term_pattern, type: Regexp, default: /[\[\]\p{Word}\._|\{\}]{2,}/
|
12
|
+
config :term_normalizer, default: default_term_normalizer do
|
13
|
+
validate do |value|
|
14
|
+
t("callable_required") unless value.respond_to?(:call)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Lita.register_handler(Lita::Handlers::Conferenz::Config)
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module Lita::Handlers::Conferenz
|
2
|
+
class Term
|
3
|
+
include Lita::Handler::Common
|
4
|
+
|
5
|
+
namespace "conferenz"
|
6
|
+
|
7
|
+
attr_reader :term
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def list_best(robot, n = 5)
|
11
|
+
list(:zrevrange, robot, n)
|
12
|
+
end
|
13
|
+
|
14
|
+
def list_worst(robot, n = 5)
|
15
|
+
list(:zrange, robot, n)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def list(redis_command, robot, n)
|
21
|
+
n = 24 if n > 24
|
22
|
+
|
23
|
+
handler = new(robot, '', normalize: false)
|
24
|
+
handler.redis.public_send(redis_command, "terms", 0, n, with_scores: true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(robot, term, normalize: true)
|
29
|
+
super(robot)
|
30
|
+
@term = normalize ? normalize_term(term) : term
|
31
|
+
@link_cache = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def check
|
35
|
+
string = "#{self}: #{total_score}"
|
36
|
+
|
37
|
+
unless links_with_scores.empty?
|
38
|
+
link_text = links_with_scores.map { |term, score| "#{term}: #{score}" }.join(", ")
|
39
|
+
string << " (#{own_score}), #{t("linked_to")}: #{link_text}"
|
40
|
+
end
|
41
|
+
|
42
|
+
string
|
43
|
+
end
|
44
|
+
|
45
|
+
def decrement(user)
|
46
|
+
modify(user, -1)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete
|
50
|
+
redis.zrem("terms", to_s)
|
51
|
+
redis.del("modified:#{self}")
|
52
|
+
redis.del("links:#{self}")
|
53
|
+
redis.smembers("linked_to:#{self}").each do |key|
|
54
|
+
redis.srem("links:#{key}", to_s)
|
55
|
+
end
|
56
|
+
redis.del("linked_to:#{self}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def eql?(other)
|
60
|
+
term.eql?(other.term)
|
61
|
+
end
|
62
|
+
alias_method :==, :eql?
|
63
|
+
|
64
|
+
def hash
|
65
|
+
term.hash
|
66
|
+
end
|
67
|
+
|
68
|
+
def increment(user)
|
69
|
+
modify(user, 1)
|
70
|
+
end
|
71
|
+
|
72
|
+
def link(other)
|
73
|
+
if config.link_conferenz_threshold
|
74
|
+
threshold = config.link_conferenz_threshold.abs
|
75
|
+
|
76
|
+
if own_score.abs < threshold || other.own_score.abs < threshold
|
77
|
+
return threshold
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
redis.sadd("links:#{self}", other.to_s) && redis.sadd("linked_to:#{other}", to_s)
|
82
|
+
end
|
83
|
+
|
84
|
+
def links
|
85
|
+
@links ||= begin
|
86
|
+
redis.smembers("links:#{self}").each do |term|
|
87
|
+
linked_term = self.class.new(robot, term)
|
88
|
+
@link_cache[linked_term.term] = linked_term
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def links_with_scores
|
94
|
+
@links_with_scores ||= begin
|
95
|
+
{}.tap do |h|
|
96
|
+
links.each do |link|
|
97
|
+
h[link] = @link_cache[link].own_score
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def modified
|
104
|
+
redis.smembers("modified:#{term}").map do |user_id|
|
105
|
+
Lita::User.find_by_id(user_id)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def own_score
|
110
|
+
@own_score ||= redis.zscore("terms", term).to_i
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_s
|
114
|
+
term
|
115
|
+
end
|
116
|
+
|
117
|
+
def total_score
|
118
|
+
@total_score ||= begin
|
119
|
+
links.inject(own_score) do |memo, linked_term|
|
120
|
+
memo + @link_cache[linked_term].own_score
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def unlink(other)
|
126
|
+
redis.srem("links:#{self}", other.to_s) && redis.srem("linked_to:#{other}", to_s)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def modify(user, delta)
|
132
|
+
ttl = redis.ttl("cooldown:#{user.id}:#{term}")
|
133
|
+
|
134
|
+
if ttl > 0
|
135
|
+
t("cooling_down", term: self, ttl: ttl, count: ttl)
|
136
|
+
else
|
137
|
+
modify!(user, delta)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def modify!(user, delta)
|
142
|
+
user_id = user.id
|
143
|
+
redis.zincrby("terms", delta, term)
|
144
|
+
redis.sadd("modified:#{self}", user_id)
|
145
|
+
redis.setex("cooldown:#{user_id}:#{self}", config.cooldown, 1) if config.cooldown
|
146
|
+
check
|
147
|
+
end
|
148
|
+
|
149
|
+
def normalize_term(term)
|
150
|
+
config.term_normalizer.call(term)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "lita-conferenz"
|
3
|
+
spec.version = "1.0.1"
|
4
|
+
spec.authors = ["Piotr Limanowski"]
|
5
|
+
spec.email = ["plimanowski+conferenz@gmail.com"]
|
6
|
+
spec.description = %q{A Lita handler for tracking conferences attendance.}
|
7
|
+
spec.summary = %q{A Lita handler for tracking conferences attendance.}
|
8
|
+
spec.homepage = "https://github.com/peel/conferenz"
|
9
|
+
spec.license = "MIT"
|
10
|
+
spec.metadata = { "lita_plugin_type" => "handler" }
|
11
|
+
|
12
|
+
spec.files = `git ls-files`.split($/)
|
13
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
14
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_runtime_dependency "lita", ">= 4.0"
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
20
|
+
spec.add_development_dependency "rake"
|
21
|
+
spec.add_development_dependency "rspec", ">= 3.0.0"
|
22
|
+
spec.add_development_dependency "simplecov"
|
23
|
+
spec.add_development_dependency "coveralls"
|
24
|
+
end
|
data/locales/en.yml
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
en:
|
2
|
+
lita:
|
3
|
+
handlers:
|
4
|
+
karma:
|
5
|
+
already_linked: "%{source} is already linked to %{target}."
|
6
|
+
already_unlinked: "%{source} is not linked to %{target}."
|
7
|
+
callable_required: must be a callable object
|
8
|
+
cooling_down:
|
9
|
+
one: "You cannot modify %{term} for another %{ttl} second."
|
10
|
+
other: "You cannot modify %{term} for another %{ttl} seconds."
|
11
|
+
delete_success: "%{term} has been deleted."
|
12
|
+
help:
|
13
|
+
increment_key: TERM++
|
14
|
+
increment_value: Increments TERM by one.
|
15
|
+
decrement_key: TERM--
|
16
|
+
decrement_value: Decrements TERM by one.
|
17
|
+
check_key: TERM~~
|
18
|
+
check_value: Shows the current karma of TERM.
|
19
|
+
link_key: TERM1 += TERM2
|
20
|
+
link_value: Links TERM2 to TERM1.
|
21
|
+
unlink_key: TERM1 -= TERM2
|
22
|
+
unlink_value: Unlinks TERM2 from TERM1.
|
23
|
+
list_worst_key: "karma worst [N]"
|
24
|
+
list_worst_value: Lists the bottom N terms by karma. N defaults to 5.
|
25
|
+
list_best_key: "karma best [N]"
|
26
|
+
list_best_value: Lists the top N terms by karma. N defaults to 5.
|
27
|
+
modified_key: karma modified TERM
|
28
|
+
modified_value: Lists the names of users who have upvoted or downvoted TERM.
|
29
|
+
delete_key: karma delete TERM
|
30
|
+
delete_value: >-
|
31
|
+
Permanently removes TERM and all its link information. TERM is matched
|
32
|
+
exactly as typed and does not adhere to the usual pattern for terms.
|
33
|
+
link_success: "%{source} has been linked to %{target}."
|
34
|
+
linked_to: linked to
|
35
|
+
never_modified: "%{term} has never been modified."
|
36
|
+
no_terms: There are no terms being tracked yet.
|
37
|
+
threshold_not_satisfied: >-
|
38
|
+
Terms must have less than or equal to -%{threshold} or greater than or equal to
|
39
|
+
%{threshold} karma to be linked or linked to.
|
40
|
+
unlink_success: "%{source} has been unlinked from %{target}."
|
@@ -0,0 +1,339 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Lita::Handlers::Conferenz::Chat, lita_handler: true do
|
5
|
+
let(:payload) { double("payload") }
|
6
|
+
|
7
|
+
prepend_before { registry.register_handler(Lita::Handlers::Conferenz::Config) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
registry.config.handlers.conferenz.cooldown = nil
|
11
|
+
registry.config.handlers.conferenz.link_karma_threshold = nil
|
12
|
+
described_class.routes.clear
|
13
|
+
subject.define_routes(payload)
|
14
|
+
end
|
15
|
+
|
16
|
+
it { is_expected.to route("foo++").to(:increment) }
|
17
|
+
it { is_expected.to route("foo--").to(:decrement) }
|
18
|
+
it { is_expected.to route("foo++ bar").to(:increment) }
|
19
|
+
it { is_expected.to route("foo-- bar").to(:decrement) }
|
20
|
+
it { is_expected.to route("foo~~").to(:check) }
|
21
|
+
it { is_expected.to route_command("conferenz best").to(:list_best) }
|
22
|
+
it { is_expected.to route_command("conferenz worst").to(:list_worst) }
|
23
|
+
it { is_expected.to route_command("conferenz modified foo").to(:modified) }
|
24
|
+
it do
|
25
|
+
is_expected.to route_command("conferenz delete").with_authorization_for(:karma_admins).to(:delete)
|
26
|
+
end
|
27
|
+
it { is_expected.to route_command("conferenz").to(:list_best) }
|
28
|
+
it { is_expected.to route_command("foo += bar").to(:link) }
|
29
|
+
it { is_expected.to route_command("foo += bar++").to(:link) }
|
30
|
+
it { is_expected.to route_command("foo += bar--").to(:link) }
|
31
|
+
it { is_expected.to route_command("foo += bar~~").to(:link) }
|
32
|
+
it { is_expected.to route_command("foo -= bar").to(:unlink) }
|
33
|
+
it { is_expected.to route_command("foo -= bar++").to(:unlink) }
|
34
|
+
it { is_expected.to route_command("foo -= bar--").to(:unlink) }
|
35
|
+
it { is_expected.to route_command("foo -= bar~~").to(:unlink) }
|
36
|
+
it { is_expected.not_to route("+++++").to(:increment) }
|
37
|
+
it { is_expected.not_to route("-----").to(:decrement) }
|
38
|
+
it { is_expected.not_to route("foo++bar").to(:increment) }
|
39
|
+
it { is_expected.not_to route("foo--bar").to(:decrement) }
|
40
|
+
|
41
|
+
describe "#increment" do
|
42
|
+
it "increases the term's score by one and says the new score" do
|
43
|
+
send_message("foo++")
|
44
|
+
expect(replies.last).to eq("foo: 1")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "matches multiple terms in one message" do
|
48
|
+
send_message("foo++ bar++")
|
49
|
+
expect(replies.last).to eq("foo: 1; bar: 1")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "doesn't start from zero if the term already has a positive score" do
|
53
|
+
send_message("foo++")
|
54
|
+
send_message("foo++")
|
55
|
+
expect(replies.last).to eq("foo: 2")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "replies with a warning if term increment is on cooldown" do
|
59
|
+
registry.config.handlers.conferenz.cooldown = 10
|
60
|
+
send_message("foo++")
|
61
|
+
send_message("foo++")
|
62
|
+
expect(replies.last).to match(/cannot modify foo/)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "is case insensitive" do
|
66
|
+
send_message("foo++")
|
67
|
+
send_message("FOO++")
|
68
|
+
expect(replies.last).to eq("foo: 2")
|
69
|
+
end
|
70
|
+
|
71
|
+
it "handles Unicode word characters" do
|
72
|
+
send_message("föö++")
|
73
|
+
expect(replies.last).to eq("föö: 1")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "#decrement" do
|
78
|
+
it "decreases the term's score by one and says the new score" do
|
79
|
+
send_message("foo--")
|
80
|
+
expect(replies.last).to eq("foo: -1")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "matches multiple terms in one message" do
|
84
|
+
send_message("foo-- bar--")
|
85
|
+
expect(replies.last).to eq("foo: -1; bar: -1")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "doesn't start from zero if the term already has a positive score" do
|
89
|
+
send_message("foo++")
|
90
|
+
send_message("foo--")
|
91
|
+
expect(replies.last).to eq("foo: 0")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "replies with a warning if term increment is on cooldown" do
|
95
|
+
registry.config.handlers.conferenz.cooldown = 10
|
96
|
+
send_message("foo--")
|
97
|
+
send_message("foo--")
|
98
|
+
expect(replies.last).to match(/cannot modify foo/)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "#check" do
|
103
|
+
it "says the term's current score" do
|
104
|
+
send_message("foo~~")
|
105
|
+
expect(replies.last).to eq("foo: 0")
|
106
|
+
end
|
107
|
+
|
108
|
+
it "matches multiple terms in one message" do
|
109
|
+
send_message("foo~~ bar~~")
|
110
|
+
expect(replies.last).to eq("foo: 0; bar: 0")
|
111
|
+
end
|
112
|
+
|
113
|
+
it "doesn't match the same term multiple times in one message" do
|
114
|
+
send_message("foo~~ foo~~")
|
115
|
+
expect(replies.size).to eq(1)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#list" do
|
120
|
+
it "replies with a warning if there are no terms" do
|
121
|
+
send_command("conferenz")
|
122
|
+
expect(replies.last).to match(/no terms being tracked/)
|
123
|
+
end
|
124
|
+
|
125
|
+
context "with modified terms" do
|
126
|
+
before do
|
127
|
+
send_message(
|
128
|
+
"one++ one++ one++ two++ two++ three++ four++ four-- five--"
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "lists the top 5 terms by default" do
|
133
|
+
send_command("conferenz")
|
134
|
+
expect(replies.last).to eq <<-MSG.chomp
|
135
|
+
1. one (3)
|
136
|
+
2. two (2)
|
137
|
+
3. three (1)
|
138
|
+
4. four (0)
|
139
|
+
5. five (-1)
|
140
|
+
MSG
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'lists the bottom 5 terms when passed "worst"' do
|
144
|
+
send_command("conferenz worst")
|
145
|
+
expect(replies.last).to eq <<-MSG.chomp
|
146
|
+
1. five (-1)
|
147
|
+
2. four (0)
|
148
|
+
3. three (1)
|
149
|
+
4. two (2)
|
150
|
+
5. one (3)
|
151
|
+
MSG
|
152
|
+
end
|
153
|
+
|
154
|
+
it "limits the list to the count passed as the second argument" do
|
155
|
+
send_command("conferenz best 2")
|
156
|
+
expect(replies.last).to eq <<-MSG.chomp
|
157
|
+
1. one (3)
|
158
|
+
2. two (2)
|
159
|
+
MSG
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe "#link" do
|
165
|
+
it "says that it's linked term 2 to term 1" do
|
166
|
+
send_command("foo += bar")
|
167
|
+
expect(replies.last).to eq("bar has been linked to foo.")
|
168
|
+
end
|
169
|
+
|
170
|
+
it "says that term 2 was already linked to term 1 if it was" do
|
171
|
+
send_command("foo += bar")
|
172
|
+
send_command("foo += bar")
|
173
|
+
expect(replies.last).to eq("bar is already linked to foo.")
|
174
|
+
end
|
175
|
+
|
176
|
+
it "causes term 1's score to be modified by term 2's" do
|
177
|
+
send_message("foo++ bar++ baz++")
|
178
|
+
send_command("foo += bar")
|
179
|
+
send_command("foo += baz")
|
180
|
+
send_message("foo~~")
|
181
|
+
expect(replies.last).to match(
|
182
|
+
/foo: 3 \(1\), linked to: ba[rz]: 1, ba[rz]: 1/
|
183
|
+
)
|
184
|
+
end
|
185
|
+
|
186
|
+
context "when link_karma_threshold is set" do
|
187
|
+
before do
|
188
|
+
registry.config.handlers.conferenz.link_karma_threshold = 1
|
189
|
+
end
|
190
|
+
|
191
|
+
it "doesn't allow a term to be linked if both are below the threshold" do
|
192
|
+
send_command("foo += bar")
|
193
|
+
expect(replies.last).to include("must have less than")
|
194
|
+
end
|
195
|
+
|
196
|
+
it "doesn't allow a term to be linked if it's below the threshold" do
|
197
|
+
send_command("foo++")
|
198
|
+
send_command("foo += bar")
|
199
|
+
expect(replies.last).to include("must have less than")
|
200
|
+
end
|
201
|
+
|
202
|
+
it "doesn't allow a term to be linked to another term below the threshold" do
|
203
|
+
send_command("bar++")
|
204
|
+
send_command("foo += bar")
|
205
|
+
expect(replies.last).to include("must have less than")
|
206
|
+
end
|
207
|
+
|
208
|
+
it "allows links if both terms meet the threshold" do
|
209
|
+
send_command("foo++ bar++")
|
210
|
+
send_command("foo += bar")
|
211
|
+
expect(replies.last).to include("has been linked")
|
212
|
+
send_command("bar += foo")
|
213
|
+
expect(replies.last).to include("has been linked")
|
214
|
+
end
|
215
|
+
|
216
|
+
it "uses the absolute value for terms with negative karma" do
|
217
|
+
send_command("foo-- bar--")
|
218
|
+
send_command("foo += bar")
|
219
|
+
expect(replies.last).to include("has been linked")
|
220
|
+
send_command("bar += foo")
|
221
|
+
expect(replies.last).to include("has been linked")
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "#unlink" do
|
227
|
+
it "says that it's unlinked term 2 from term 1" do
|
228
|
+
send_command("foo += bar")
|
229
|
+
send_command("foo -= bar")
|
230
|
+
expect(replies.last).to eq("bar has been unlinked from foo.")
|
231
|
+
end
|
232
|
+
|
233
|
+
it "says that term 2 was not linked to term 1 if it wasn't" do
|
234
|
+
send_command("foo -= bar")
|
235
|
+
expect(replies.last).to eq("bar is not linked to foo.")
|
236
|
+
end
|
237
|
+
|
238
|
+
it "causes term 1's score to stop being modified by term 2's" do
|
239
|
+
send_message("foo++ bar++")
|
240
|
+
send_command("foo += bar")
|
241
|
+
send_command("foo -= bar")
|
242
|
+
send_message("foo~~")
|
243
|
+
expect(replies.last).to eq("foo: 1")
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
describe "#modified" do
|
248
|
+
it "replies with a message if the term hasn't been modified" do
|
249
|
+
send_command("conferenz modified foo")
|
250
|
+
expect(replies.last).to match(/never been modified/)
|
251
|
+
end
|
252
|
+
|
253
|
+
it "lists users who have modified the given term in count order" do
|
254
|
+
other_user = Lita::User.create("2", name: "Other User")
|
255
|
+
send_message("foo++", as: user)
|
256
|
+
send_message("foo++", as: user)
|
257
|
+
send_message("foo++", as: other_user)
|
258
|
+
send_command("conferenz modified foo")
|
259
|
+
expect(replies.last).to eq("#{user.name}, #{other_user.name}")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe "#delete" do
|
264
|
+
before do
|
265
|
+
robot.auth.add_user_to_group!(user, :karma_admins)
|
266
|
+
end
|
267
|
+
|
268
|
+
it "deletes the term" do
|
269
|
+
send_message("foo++")
|
270
|
+
send_command("conferenz delete foo")
|
271
|
+
expect(replies.last).to eq("foo has been deleted.")
|
272
|
+
send_message("foo~~")
|
273
|
+
expect(replies.last).to eq("foo: 0")
|
274
|
+
end
|
275
|
+
|
276
|
+
it "matches terms exactly, including leading whitespace" do
|
277
|
+
term = " 'foo bar* 'baz''/ :"
|
278
|
+
subject.redis.zincrby("terms", 1, term)
|
279
|
+
send_command("conferenz delete #{term}")
|
280
|
+
expect(replies.last).to include("has been deleted")
|
281
|
+
end
|
282
|
+
|
283
|
+
it "clears the modification list" do
|
284
|
+
send_message("foo++")
|
285
|
+
send_command("conferenz delete foo")
|
286
|
+
send_command("conferenz modified foo")
|
287
|
+
expect(replies.last).to eq("foo has never been modified.")
|
288
|
+
end
|
289
|
+
|
290
|
+
it "clears the deleted term's links" do
|
291
|
+
send_command("foo += bar")
|
292
|
+
send_command("foo += baz")
|
293
|
+
send_command("conferenz delete foo")
|
294
|
+
send_message("foo++")
|
295
|
+
expect(replies.last).to eq("foo: 1")
|
296
|
+
end
|
297
|
+
|
298
|
+
it "clears links from other terms connected to the deleted term" do
|
299
|
+
send_command("bar += foo")
|
300
|
+
send_command("baz += foo")
|
301
|
+
send_command("conferenz delete foo")
|
302
|
+
send_message("bar++")
|
303
|
+
expect(replies.last).to eq("bar: 1")
|
304
|
+
send_message("baz++")
|
305
|
+
expect(replies.last).to eq("baz: 1")
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
describe "custom term patterns and normalization" do
|
310
|
+
before do
|
311
|
+
registry.config.handlers.conferenz.term_pattern = /[<:]([^>:]+)[>:]/
|
312
|
+
registry.config.handlers.conferenz.term_normalizer = lambda do |term|
|
313
|
+
term.to_s.downcase.strip.sub(/[<:]([^>:]+)[>:]/, '\1')
|
314
|
+
end
|
315
|
+
described_class.routes.clear
|
316
|
+
subject.define_routes(payload)
|
317
|
+
end
|
318
|
+
|
319
|
+
it "increments multi-word terms bounded by delimeters" do
|
320
|
+
send_message(":Some Thing:++")
|
321
|
+
expect(replies.last).to eq("some thing: 1")
|
322
|
+
end
|
323
|
+
|
324
|
+
it "increments terms with symbols that are bounded by delimeters" do
|
325
|
+
send_message("<C++>++")
|
326
|
+
expect(replies.last).to eq("c++: 1")
|
327
|
+
end
|
328
|
+
|
329
|
+
it "decrements multi-word terms bounded by delimeters" do
|
330
|
+
send_message(":Some Thing:--")
|
331
|
+
expect(replies.last).to eq("some thing: -1")
|
332
|
+
end
|
333
|
+
|
334
|
+
it "checks multi-word terms bounded by delimeters" do
|
335
|
+
send_message(":Some Thing:~~")
|
336
|
+
expect(replies.last).to eq("some thing: 0")
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
require "coveralls"
|
3
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
4
|
+
SimpleCov::Formatter::HTMLFormatter,
|
5
|
+
Coveralls::SimpleCov::Formatter
|
6
|
+
]
|
7
|
+
SimpleCov.start { add_filter "/spec/" }
|
8
|
+
|
9
|
+
require "lita-conferenz"
|
10
|
+
require "lita/rspec"
|
11
|
+
|
12
|
+
Lita.version_3_compatibility_mode = false
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lita-conferenz
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Piotr Limanowski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: lita
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coveralls
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: A Lita handler for tracking conferences attendance.
|
98
|
+
email:
|
99
|
+
- plimanowski+conferenz@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- Gemfile
|
106
|
+
- Rakefile
|
107
|
+
- lib/lita-conferenz.rb
|
108
|
+
- lib/lita/handlers/conferenz/chat.rb
|
109
|
+
- lib/lita/handlers/conferenz/config.rb
|
110
|
+
- lib/lita/handlers/conferenz/term.rb
|
111
|
+
- lita-conferenz.gemspec
|
112
|
+
- locales/en.yml
|
113
|
+
- spec/lita/handlers/conferenz/chat_spec.rb
|
114
|
+
- spec/spec_helper.rb
|
115
|
+
homepage: https://github.com/peel/conferenz
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata:
|
119
|
+
lita_plugin_type: handler
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 2.4.8
|
137
|
+
signing_key:
|
138
|
+
specification_version: 4
|
139
|
+
summary: A Lita handler for tracking conferences attendance.
|
140
|
+
test_files:
|
141
|
+
- spec/lita/handlers/conferenz/chat_spec.rb
|
142
|
+
- spec/spec_helper.rb
|