lita-karma 0.0.3 → 1.0.0
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 +4 -4
- data/lib/lita/handlers/karma.rb +64 -60
- data/lita-karma.gemspec +2 -2
- data/spec/lita/handlers/karma_spec.rb +71 -77
- data/spec/spec_helper.rb +1 -1
- metadata +4 -5
- data/CHANGELOG.md +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 666d042af696f1eb6b735cd531c3ea31a8485987
|
4
|
+
data.tar.gz: c445dfd3e021e96dda584674ba75953fdf28335f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0863e9bd71df183c70fa99ef8f6070839016f5b174496913c41406ea38ccf3ff6219bf63b79746748e294020accd09e553c2475b74890cb58b32a4845d039e39
|
7
|
+
data.tar.gz: 0c39702837bd97f9cabca8fb076e97a9db599d940007fff164690d8fe1c2f5d3993e79004f35f7355ab329fb8d92b5172a48d27903572cd576c95ddb22462cbf
|
data/lib/lita/handlers/karma.rb
CHANGED
@@ -3,43 +3,42 @@ require "lita"
|
|
3
3
|
module Lita
|
4
4
|
module Handlers
|
5
5
|
class Karma < Handler
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
6
|
+
route %r{([^\s]{2,})\+\+}, :increment, help: { "TERM++" => "Increments TERM by one." }
|
7
|
+
route %r{([^\s]{2,})\-\-}, :decrement, help: { "TERM--" => "Decrements TERM by one." }
|
8
|
+
route %r{([^\s]{2,})~~}, :check, help: { "TERM~~" => "Shows the current karma of TERM." }
|
9
|
+
route %r{^karma\s+worst}, :list_worst, command: true, help: {
|
10
|
+
"karma worst [N]" => "Lists the bottom N terms by karma. N defaults to 5."
|
11
|
+
}
|
12
|
+
route %r{^karma\s+best}, :list_best, command: true, help: {
|
13
|
+
"karma best [N]" => "Lists the top N terms by karma. N defaults to 5."
|
14
|
+
}
|
15
|
+
route %r{^karma\s+modified}, :modified, command: true, help: {
|
16
|
+
"karma modified TERM" => "Lists the names of users who have upvoted or downvoted TERM."
|
17
|
+
}
|
18
|
+
route %r{^karma\s*$}, :list_best, command: true
|
19
|
+
route %r{^([^\s]{2,})\s*\+=\s*([^\s]{2,})}, :link, command: true, help: {
|
20
|
+
"TERM1 += TERM2" => "Links TERM2 to TERM1. TERM1's karma will then be displayed as the sum of its own and TERM2's karma."
|
21
|
+
}
|
22
|
+
route %r{^([^\s]{2,})\s*-=\s*([^\s]{2,})}, :unlink, command: true, help: {
|
23
|
+
"TERM1 -= TERM2" => "Unlinks TERM2 from TERM1. TERM1's karma will no longer be displayed as the sum of its own and TERM2's karma."
|
24
|
+
}
|
25
|
+
|
26
|
+
def self.default_config(config)
|
27
|
+
config.cooldown = 300
|
19
28
|
end
|
20
29
|
|
21
|
-
|
22
|
-
|
23
|
-
route %r{([^\s]{2,})~~}, to: :check
|
24
|
-
route %r{^karma\s+worst}, to: :list_worst, command: true
|
25
|
-
route %r{^karma\s+best}, to: :list_best, command: true
|
26
|
-
route %r{^karma\s+modified}, to: :modified, command: true
|
27
|
-
route %r{^karma\s*$}, to: :list_best, command: true
|
28
|
-
route %r{^([^\s]{2,})\s*\+=\s*([^\s]{2,})}, to: :link, command: true
|
29
|
-
route %r{^([^\s]{2,})\s*-=\s*([^\s]{2,})}, to: :unlink, command: true
|
30
|
-
|
31
|
-
def increment(matches)
|
32
|
-
modify(matches, 1)
|
30
|
+
def increment(response)
|
31
|
+
modify(response, 1)
|
33
32
|
end
|
34
33
|
|
35
|
-
def decrement(
|
36
|
-
modify(
|
34
|
+
def decrement(response)
|
35
|
+
modify(response, -1)
|
37
36
|
end
|
38
37
|
|
39
|
-
def check(
|
38
|
+
def check(response)
|
40
39
|
output = []
|
41
40
|
|
42
|
-
matches.each do |match|
|
41
|
+
response.matches.each do |match|
|
43
42
|
term = match[0]
|
44
43
|
own_score = score = redis.zscore("terms", term).to_i
|
45
44
|
links = []
|
@@ -57,86 +56,93 @@ module Lita
|
|
57
56
|
output << string
|
58
57
|
end
|
59
58
|
|
60
|
-
reply *output
|
59
|
+
response.reply *output
|
61
60
|
end
|
62
61
|
|
63
|
-
def list_best(
|
64
|
-
list(:zrevrange)
|
62
|
+
def list_best(response)
|
63
|
+
list(response, :zrevrange)
|
65
64
|
end
|
66
65
|
|
67
|
-
def list_worst(
|
68
|
-
list(:zrange)
|
66
|
+
def list_worst(response)
|
67
|
+
list(response, :zrange)
|
69
68
|
end
|
70
69
|
|
71
|
-
def link(
|
72
|
-
matches.each do |match|
|
70
|
+
def link(response)
|
71
|
+
response.matches.each do |match|
|
73
72
|
term1, term2 = match
|
74
73
|
|
75
74
|
if redis.sadd("links:#{term1}", term2)
|
76
|
-
reply "#{term2} has been linked to #{term1}."
|
75
|
+
response.reply "#{term2} has been linked to #{term1}."
|
77
76
|
else
|
78
|
-
reply "#{term2} is already linked to #{term1}."
|
77
|
+
response.reply "#{term2} is already linked to #{term1}."
|
79
78
|
end
|
80
79
|
end
|
81
80
|
end
|
82
81
|
|
83
|
-
def unlink(
|
84
|
-
matches.each do |match|
|
82
|
+
def unlink(response)
|
83
|
+
response.matches.each do |match|
|
85
84
|
term1, term2 = match
|
86
85
|
|
87
86
|
if redis.srem("links:#{term1}", term2)
|
88
|
-
reply "#{term2} has been unlinked from #{term1}."
|
87
|
+
response.reply "#{term2} has been unlinked from #{term1}."
|
89
88
|
else
|
90
|
-
reply "#{term2} is not linked to #{term1}."
|
89
|
+
response.reply "#{term2} is not linked to #{term1}."
|
91
90
|
end
|
92
91
|
end
|
93
92
|
end
|
94
93
|
|
95
|
-
def modified(
|
96
|
-
term = args[1]
|
94
|
+
def modified(response)
|
95
|
+
term = response.args[1]
|
97
96
|
|
98
97
|
if term.nil? || term.strip.empty?
|
99
|
-
reply "Format: #{robot.name}: karma modified TERM"
|
98
|
+
response.reply "Format: #{robot.name}: karma modified TERM"
|
100
99
|
return
|
101
100
|
end
|
102
101
|
|
103
102
|
user_ids = redis.smembers("modified:#{term}")
|
104
103
|
|
105
104
|
if user_ids.empty?
|
106
|
-
reply "#{term} has never been modified."
|
105
|
+
response.reply "#{term} has never been modified."
|
107
106
|
else
|
108
|
-
|
107
|
+
output = user_ids.map do |id|
|
108
|
+
User.find_by_id(id).name
|
109
|
+
end.join(", ")
|
110
|
+
response.reply output
|
109
111
|
end
|
110
112
|
end
|
111
113
|
|
112
114
|
private
|
113
115
|
|
114
|
-
def modify(
|
115
|
-
matches.each do |match|
|
116
|
+
def modify(response, delta)
|
117
|
+
response.matches.each do |match|
|
116
118
|
term = match[0]
|
117
119
|
|
118
|
-
ttl = redis.ttl("cooldown:#{user.id}:#{term}")
|
120
|
+
ttl = redis.ttl("cooldown:#{response.user.id}:#{term}")
|
119
121
|
if ttl >= 0
|
120
122
|
cooldown_message =
|
121
123
|
"You cannot modify #{term} for another #{ttl} second"
|
122
124
|
cooldown_message << (ttl == 1 ? "." : "s.")
|
123
|
-
reply cooldown_message
|
125
|
+
response.reply cooldown_message
|
124
126
|
return
|
125
127
|
else
|
126
128
|
redis.zincrby("terms", delta, term)
|
127
|
-
redis.sadd("modified:#{term}", user.id)
|
129
|
+
redis.sadd("modified:#{term}", response.user.id)
|
128
130
|
cooldown = Lita.config.handlers.karma.cooldown
|
129
131
|
if cooldown
|
130
|
-
redis.setex(
|
132
|
+
redis.setex(
|
133
|
+
"cooldown:#{response.user.id}:#{term}",
|
134
|
+
cooldown.to_i,
|
135
|
+
1
|
136
|
+
)
|
131
137
|
end
|
132
138
|
end
|
133
139
|
end
|
134
140
|
|
135
|
-
check(
|
141
|
+
check(response)
|
136
142
|
end
|
137
143
|
|
138
|
-
def list(redis_command)
|
139
|
-
n = (args[1] || 5).to_i - 1
|
144
|
+
def list(response, redis_command)
|
145
|
+
n = (response.args[1] || 5).to_i - 1
|
140
146
|
|
141
147
|
terms_scores = redis.public_send(
|
142
148
|
redis_command, "terms", 0, n, with_scores: true
|
@@ -147,15 +153,13 @@ module Lita
|
|
147
153
|
end.join("\n")
|
148
154
|
|
149
155
|
if output.length == 0
|
150
|
-
reply "There are no terms being tracked yet."
|
156
|
+
response.reply "There are no terms being tracked yet."
|
151
157
|
else
|
152
|
-
reply output
|
158
|
+
response.reply output
|
153
159
|
end
|
154
160
|
end
|
155
161
|
end
|
156
162
|
|
157
|
-
Lita.config.handlers.karma = Config.new
|
158
|
-
Lita.config.handlers.karma.cooldown = 300
|
159
163
|
Lita.register_handler(Karma)
|
160
164
|
end
|
161
165
|
end
|
data/lita-karma.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "lita-karma"
|
3
|
-
spec.version = "0.0
|
3
|
+
spec.version = "1.0.0"
|
4
4
|
spec.authors = ["Jimmy Cuadra"]
|
5
5
|
spec.email = ["jimmy@jimmycuadra.com"]
|
6
6
|
spec.description = %q{A Lita handler for tracking karma points for arbitrary terms.}
|
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
14
14
|
spec.require_paths = ["lib"]
|
15
15
|
|
16
|
-
spec.add_runtime_dependency "lita", "~>
|
16
|
+
spec.add_runtime_dependency "lita", "~> 2.0"
|
17
17
|
|
18
18
|
spec.add_development_dependency "bundler", "~> 1.3"
|
19
19
|
spec.add_development_dependency "rake"
|
@@ -1,196 +1,190 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
describe Lita::Handlers::Karma,
|
3
|
+
describe Lita::Handlers::Karma, lita_handler: true do
|
4
4
|
before { Lita.config.handlers.karma.cooldown = nil }
|
5
5
|
|
6
6
|
it { routes("foo++").to(:increment) }
|
7
7
|
it { routes("foo--").to(:decrement) }
|
8
8
|
it { routes("foo~~").to(:check) }
|
9
|
-
it {
|
10
|
-
it {
|
11
|
-
it {
|
12
|
-
it {
|
13
|
-
it {
|
14
|
-
it {
|
15
|
-
|
16
|
-
describe ".help" do
|
17
|
-
it "returns a hash of command help" do
|
18
|
-
expect(described_class.help).to be_a(Hash)
|
19
|
-
end
|
20
|
-
end
|
9
|
+
it { routes_command("karma best").to(:list_best) }
|
10
|
+
it { routes_command("karma worst").to(:list_worst) }
|
11
|
+
it { routes_command("karma modified").to(:modified) }
|
12
|
+
it { routes_command("karma").to(:list_best) }
|
13
|
+
it { routes_command("foo += bar").to(:link) }
|
14
|
+
it { routes_command("foo -= bar").to(:unlink) }
|
21
15
|
|
22
16
|
describe "#increment" do
|
23
17
|
it "increases the term's score by one and says the new score" do
|
24
|
-
|
25
|
-
|
18
|
+
send_message("foo++")
|
19
|
+
expect(replies.last).to eq("foo: 1")
|
26
20
|
end
|
27
21
|
|
28
22
|
it "matches multiple terms in one message" do
|
29
|
-
|
30
|
-
|
23
|
+
send_message("foo++ bar++")
|
24
|
+
expect(replies).to eq(["foo: 1", "bar: 1"])
|
31
25
|
end
|
32
26
|
|
33
27
|
it "doesn't start from zero if the term already has a positive score" do
|
34
|
-
|
35
|
-
|
36
|
-
|
28
|
+
send_message("foo++")
|
29
|
+
send_message("foo++")
|
30
|
+
expect(replies.last).to eq("foo: 2")
|
37
31
|
end
|
38
32
|
|
39
33
|
it "replies with a warning if term increment is on cooldown" do
|
40
34
|
Lita.config.handlers.karma.cooldown = 10
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
send_test_message("foo++")
|
35
|
+
send_message("foo++")
|
36
|
+
send_message("foo++")
|
37
|
+
expect(replies.last).to match(/cannot modify foo/)
|
45
38
|
end
|
46
39
|
end
|
47
40
|
|
48
41
|
describe "#decrement" do
|
49
42
|
it "decreases the term's score by one and says the new score" do
|
50
|
-
|
51
|
-
|
43
|
+
send_message("foo--")
|
44
|
+
expect(replies.last).to eq("foo: -1")
|
52
45
|
end
|
53
46
|
|
54
47
|
it "matches multiple terms in one message" do
|
55
|
-
|
56
|
-
|
48
|
+
send_message("foo-- bar--")
|
49
|
+
expect(replies).to eq(["foo: -1", "bar: -1"])
|
57
50
|
end
|
58
51
|
|
59
52
|
it "doesn't start from zero if the term already has a positive score" do
|
60
|
-
|
61
|
-
|
62
|
-
|
53
|
+
send_message("foo++")
|
54
|
+
send_message("foo--")
|
55
|
+
expect(replies.last).to eq("foo: 0")
|
63
56
|
end
|
64
57
|
|
65
58
|
it "replies with a warning if term increment is on cooldown" do
|
66
59
|
Lita.config.handlers.karma.cooldown = 10
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
send_test_message("foo--")
|
60
|
+
send_message("foo--")
|
61
|
+
send_message("foo--")
|
62
|
+
expect(replies.last).to match(/cannot modify foo/)
|
71
63
|
end
|
72
64
|
end
|
73
65
|
|
74
66
|
describe "#check" do
|
75
67
|
it "says the term's current score" do
|
76
|
-
|
77
|
-
|
68
|
+
send_message("foo~~")
|
69
|
+
expect(replies.last).to eq("foo: 0")
|
78
70
|
end
|
79
71
|
|
80
72
|
it "matches multiple terms in one message" do
|
81
|
-
|
82
|
-
|
73
|
+
send_message("foo~~ bar~~")
|
74
|
+
expect(replies).to eq(["foo: 0", "bar: 0"])
|
83
75
|
end
|
84
76
|
end
|
85
77
|
|
86
78
|
describe "#list" do
|
87
79
|
it "replies with a warning if there are no terms" do
|
88
|
-
|
89
|
-
|
80
|
+
send_command("karma")
|
81
|
+
expect(replies.last).to match(/no terms being tracked/)
|
90
82
|
end
|
91
83
|
|
92
84
|
context "with modified terms" do
|
93
85
|
before do
|
94
|
-
|
86
|
+
send_message(
|
95
87
|
"one++ one++ one++ two++ two++ three++ four++ four-- five--"
|
96
88
|
)
|
97
89
|
end
|
98
90
|
|
99
91
|
it "lists the top 5 terms by default" do
|
100
|
-
|
92
|
+
send_command("karma")
|
93
|
+
expect(replies.last).to eq <<-MSG.chomp
|
101
94
|
1. one (3)
|
102
95
|
2. two (2)
|
103
96
|
3. three (1)
|
104
97
|
4. four (0)
|
105
98
|
5. five (-1)
|
106
99
|
MSG
|
107
|
-
send_test_message("#{robot.name}: karma")
|
108
100
|
end
|
109
101
|
|
110
102
|
it 'lists the bottom 5 terms when passed "worst"' do
|
111
|
-
|
103
|
+
send_command("karma worst")
|
104
|
+
expect(replies.last).to eq <<-MSG.chomp
|
112
105
|
1. five (-1)
|
113
106
|
2. four (0)
|
114
107
|
3. three (1)
|
115
108
|
4. two (2)
|
116
109
|
5. one (3)
|
117
110
|
MSG
|
118
|
-
send_test_message("#{robot.name}: karma worst")
|
119
111
|
end
|
120
112
|
|
121
113
|
it "limits the list to the count passed as the second argument" do
|
122
|
-
|
114
|
+
send_command("karma best 2")
|
115
|
+
expect(replies.last).to eq <<-MSG.chomp
|
123
116
|
1. one (3)
|
124
117
|
2. two (2)
|
125
118
|
MSG
|
126
|
-
send_test_message("#{robot.name}: karma best 2")
|
127
119
|
end
|
128
120
|
end
|
129
121
|
end
|
130
122
|
|
131
123
|
describe "#link" do
|
132
124
|
it "says that it's linked term 2 to term 1" do
|
133
|
-
|
134
|
-
|
125
|
+
send_command("foo += bar")
|
126
|
+
expect(replies.last).to eq("bar has been linked to foo.")
|
135
127
|
end
|
136
128
|
|
137
129
|
it "says that term 2 was already linked to term 1 if it was" do
|
138
|
-
|
139
|
-
|
140
|
-
|
130
|
+
send_command("foo += bar")
|
131
|
+
send_command("foo += bar")
|
132
|
+
expect(replies.last).to eq("bar is already linked to foo.")
|
141
133
|
end
|
142
134
|
|
143
135
|
it "causes term 1's score to be modified by term 2's" do
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
136
|
+
send_message("foo++ bar++ baz++")
|
137
|
+
send_command("foo += bar")
|
138
|
+
send_command("foo += baz")
|
139
|
+
send_message("foo~~")
|
140
|
+
expect(replies.last).to match(
|
141
|
+
/foo: 3 \(1\), linked to: ba[rz]: 1, ba[rz]: 1/
|
142
|
+
)
|
149
143
|
end
|
150
144
|
end
|
151
145
|
|
152
146
|
describe "#unlink" do
|
153
147
|
it "says that it's unlinked term 2 from term 1" do
|
154
|
-
|
155
|
-
|
156
|
-
|
148
|
+
send_command("foo += bar")
|
149
|
+
send_command("foo -= bar")
|
150
|
+
expect(replies.last).to eq("bar has been unlinked from foo.")
|
157
151
|
end
|
158
152
|
|
159
153
|
it "says that term 2 was not linked to term 1 if it wasn't" do
|
160
|
-
|
161
|
-
|
154
|
+
send_command("foo -= bar")
|
155
|
+
expect(replies.last).to eq("bar is not linked to foo.")
|
162
156
|
end
|
163
157
|
|
164
158
|
it "causes term 1's score to stop being modified by term 2's" do
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
159
|
+
send_message("foo++ bar++")
|
160
|
+
send_command("foo += bar")
|
161
|
+
send_command("foo -= bar")
|
162
|
+
send_message("foo~~")
|
163
|
+
expect(replies.last).to eq("foo: 1")
|
170
164
|
end
|
171
165
|
end
|
172
166
|
|
173
167
|
describe "#modified" do
|
174
168
|
it "replies with the required format if a term is not provided" do
|
175
|
-
|
176
|
-
|
169
|
+
send_command("karma modified")
|
170
|
+
expect(replies.last).to match(/^Format:/)
|
177
171
|
end
|
178
172
|
|
179
173
|
it "replies with the required format if the term is an empty string" do
|
180
|
-
|
181
|
-
|
174
|
+
send_command("karma modified ' '")
|
175
|
+
expect(replies.last).to match(/^Format:/)
|
182
176
|
end
|
183
177
|
|
184
178
|
it "replies with a message if the term hasn't been modified" do
|
185
|
-
|
186
|
-
|
179
|
+
send_command("karma modified foo")
|
180
|
+
expect(replies.last).to match(/never been modified/)
|
187
181
|
end
|
188
182
|
|
189
183
|
it "lists users who have modified the given term" do
|
190
184
|
allow(Lita::User).to receive(:find_by_id).and_return(user)
|
191
|
-
|
192
|
-
|
193
|
-
|
185
|
+
send_message("foo++")
|
186
|
+
send_command("karma modified foo")
|
187
|
+
expect(replies.last).to eq(user.name)
|
194
188
|
end
|
195
189
|
end
|
196
190
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lita-karma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jimmy Cuadra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-06
|
11
|
+
date: 2013-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: lita
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -103,7 +103,6 @@ extra_rdoc_files: []
|
|
103
103
|
files:
|
104
104
|
- .gitignore
|
105
105
|
- .travis.yml
|
106
|
-
- CHANGELOG.md
|
107
106
|
- Gemfile
|
108
107
|
- README.md
|
109
108
|
- Rakefile
|
data/CHANGELOG.md
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
# Changelog
|
2
|
-
|
3
|
-
## 0.0.3 (June 23, 2013)
|
4
|
-
|
5
|
-
* Added command help.
|
6
|
-
* When a user attempts to modify a term and is rate limited, Lita no longer also displays the term's current karma.
|
7
|
-
|
8
|
-
## 0.0.2 (June 22, 2013)
|
9
|
-
|
10
|
-
* Added term linking.
|
11
|
-
* Added `karma modified` command.
|
12
|
-
* Added rate limiting via `config.handlers.karma.cooldown`.
|
13
|
-
|
14
|
-
## 0.0.1 (June 15, 2003)
|
15
|
-
|
16
|
-
Initial release.
|