tkellem 0.9.3 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Gemfile.lock +26 -31
- data/lib/tkellem/bouncer.rb +15 -1
- data/lib/tkellem/irc_message.rb +3 -0
- data/lib/tkellem/plugins/backlog.rb +79 -12
- data/lib/tkellem/tkellem_bot.rb +1 -1
- data/lib/tkellem/tkellem_server.rb +1 -1
- data/lib/tkellem/version.rb +1 -1
- data/spec/irc_message_spec.rb +2 -2
- data/spec/plugins/backlog_spec.rb +22 -0
- data/tkellem.gemspec +2 -0
- metadata +35 -8
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,52 +1,55 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tkellem (0.9.
|
4
|
+
tkellem (0.9.4)
|
5
5
|
activerecord (~> 4.0.0.rc2)
|
6
6
|
eventmachine (~> 1.0.3)
|
7
|
+
geoip (~> 1.3.2)
|
7
8
|
rails-observers (~> 0.1.1)
|
8
9
|
sqlite3 (~> 1.3.3)
|
10
|
+
tzinfo
|
9
11
|
|
10
12
|
GEM
|
11
13
|
remote: https://rubygems.org/
|
12
14
|
specs:
|
13
|
-
|
14
|
-
activesupport (= 4.0.
|
15
|
+
activemodel (4.0.1.rc3)
|
16
|
+
activesupport (= 4.0.1.rc3)
|
15
17
|
builder (~> 3.1.0)
|
16
|
-
|
17
|
-
|
18
|
-
rack-test (~> 0.6.2)
|
19
|
-
activemodel (4.0.0.rc2)
|
20
|
-
activesupport (= 4.0.0.rc2)
|
21
|
-
builder (~> 3.1.0)
|
22
|
-
activerecord (4.0.0.rc2)
|
23
|
-
activemodel (= 4.0.0.rc2)
|
18
|
+
activerecord (4.0.1.rc3)
|
19
|
+
activemodel (= 4.0.1.rc3)
|
24
20
|
activerecord-deprecated_finders (~> 1.0.2)
|
25
|
-
activesupport (= 4.0.
|
21
|
+
activesupport (= 4.0.1.rc3)
|
26
22
|
arel (~> 4.0.0)
|
27
23
|
activerecord-deprecated_finders (1.0.3)
|
28
|
-
activesupport (4.0.
|
24
|
+
activesupport (4.0.1.rc3)
|
29
25
|
i18n (~> 0.6, >= 0.6.4)
|
30
26
|
minitest (~> 4.2)
|
31
27
|
multi_json (~> 1.3)
|
32
28
|
thread_safe (~> 0.1)
|
33
29
|
tzinfo (~> 0.3.37)
|
34
|
-
arel (4.0.
|
35
|
-
atomic (1.1.
|
30
|
+
arel (4.0.1)
|
31
|
+
atomic (1.1.14)
|
36
32
|
builder (3.1.4)
|
37
33
|
coderay (1.0.9)
|
38
34
|
colorize (0.5.8)
|
35
|
+
columnize (0.3.6)
|
39
36
|
coveralls (0.6.7)
|
40
37
|
colorize
|
41
38
|
multi_json (~> 1.3)
|
42
39
|
rest-client
|
43
40
|
simplecov (>= 0.7)
|
44
41
|
thor
|
42
|
+
debugger (1.6.2)
|
43
|
+
columnize (>= 0.3.1)
|
44
|
+
debugger-linecache (~> 1.2.0)
|
45
|
+
debugger-ruby_core_source (~> 1.2.3)
|
46
|
+
debugger-linecache (1.2.0)
|
47
|
+
debugger-ruby_core_source (1.2.3)
|
45
48
|
diff-lcs (1.2.4)
|
46
|
-
erubis (2.7.0)
|
47
49
|
eventmachine (1.0.3)
|
48
50
|
ffi (1.9.0)
|
49
51
|
formatador (0.2.4)
|
52
|
+
geoip (1.3.3)
|
50
53
|
guard (1.8.1)
|
51
54
|
formatador (>= 0.2.4)
|
52
55
|
listen (>= 1.0.0)
|
@@ -56,7 +59,7 @@ GEM
|
|
56
59
|
guard-rspec (3.0.2)
|
57
60
|
guard (>= 1.8)
|
58
61
|
rspec (~> 2.13)
|
59
|
-
i18n (0.6.
|
62
|
+
i18n (0.6.5)
|
60
63
|
listen (1.2.2)
|
61
64
|
rb-fsevent (>= 0.9.3)
|
62
65
|
rb-inotify (>= 0.9)
|
@@ -73,17 +76,8 @@ GEM
|
|
73
76
|
coderay (~> 1.0.5)
|
74
77
|
method_source (~> 0.8)
|
75
78
|
slop (~> 3.4)
|
76
|
-
|
77
|
-
|
78
|
-
rack (>= 1.0)
|
79
|
-
rails-observers (0.1.1)
|
80
|
-
railties (~> 4.0.0.beta)
|
81
|
-
railties (4.0.0.rc2)
|
82
|
-
actionpack (= 4.0.0.rc2)
|
83
|
-
activesupport (= 4.0.0.rc2)
|
84
|
-
rake (>= 0.8.7)
|
85
|
-
thor (>= 0.18.1, < 2.0)
|
86
|
-
rake (10.1.0)
|
79
|
+
rails-observers (0.1.2)
|
80
|
+
activemodel (~> 4.0)
|
87
81
|
rb-fsevent (0.9.3)
|
88
82
|
rb-inotify (0.9.0)
|
89
83
|
ffi (>= 0.5.0)
|
@@ -107,18 +101,19 @@ GEM
|
|
107
101
|
simplecov-html (~> 0.7.1)
|
108
102
|
simplecov-html (0.7.1)
|
109
103
|
slop (3.4.5)
|
110
|
-
sqlite3 (1.3.
|
104
|
+
sqlite3 (1.3.8)
|
111
105
|
terminal-notifier (1.4.2)
|
112
106
|
thor (0.18.1)
|
113
|
-
thread_safe (0.1.
|
107
|
+
thread_safe (0.1.3)
|
114
108
|
atomic
|
115
|
-
tzinfo (0.3.
|
109
|
+
tzinfo (0.3.38)
|
116
110
|
|
117
111
|
PLATFORMS
|
118
112
|
ruby
|
119
113
|
|
120
114
|
DEPENDENCIES
|
121
115
|
coveralls
|
116
|
+
debugger
|
122
117
|
guard-rspec
|
123
118
|
mocha (= 0.14.0)
|
124
119
|
rspec (~> 2.13.0)
|
data/lib/tkellem/bouncer.rb
CHANGED
@@ -29,6 +29,7 @@ class Bouncer
|
|
29
29
|
@data = {}
|
30
30
|
# clients waiting for us to connect to the irc server
|
31
31
|
@waiting_clients = []
|
32
|
+
@awaiting_replies = {}
|
32
33
|
|
33
34
|
connect!
|
34
35
|
end
|
@@ -91,6 +92,8 @@ class Bouncer
|
|
91
92
|
when 'NICK'
|
92
93
|
@nick = msg.args.last
|
93
94
|
true
|
95
|
+
when 'WHO'
|
96
|
+
client
|
94
97
|
else
|
95
98
|
true
|
96
99
|
end
|
@@ -98,9 +101,14 @@ class Bouncer
|
|
98
101
|
if forward
|
99
102
|
# send to server
|
100
103
|
send_msg(msg)
|
104
|
+
flag_for_reply(msg.command, forward) if forward != true
|
101
105
|
end
|
102
106
|
end
|
103
107
|
|
108
|
+
def flag_for_reply(command, conn)
|
109
|
+
@awaiting_replies[command] = conn
|
110
|
+
end
|
111
|
+
|
104
112
|
def server_msg(msg)
|
105
113
|
return if plugins.any? do |plugin|
|
106
114
|
!plugin.server_msg(self, msg)
|
@@ -154,13 +162,19 @@ class Bouncer
|
|
154
162
|
@nick = msg.args.last
|
155
163
|
end
|
156
164
|
true
|
165
|
+
when '352'
|
166
|
+
@awaiting_replies['WHO'] || true
|
167
|
+
when '315'
|
168
|
+
@awaiting_replies.delete('WHO') || true
|
157
169
|
else
|
158
170
|
true
|
159
171
|
end
|
160
172
|
|
161
|
-
if forward
|
173
|
+
if forward == true
|
162
174
|
# send to clients
|
163
175
|
@active_conns.each { |c,s| c.send_msg(msg) }
|
176
|
+
elsif forward && @active_conns.include?(forward)
|
177
|
+
forward.send_msg(msg)
|
164
178
|
end
|
165
179
|
end
|
166
180
|
|
data/lib/tkellem/irc_message.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'backwards_file_reader'
|
3
3
|
require 'fileutils'
|
4
|
+
require 'geoip'
|
4
5
|
require 'pathname'
|
5
6
|
require 'time'
|
6
7
|
|
7
8
|
require 'active_support/core_ext/class/attribute_accessors'
|
9
|
+
require 'active_support/core_ext/time'
|
8
10
|
|
9
11
|
require 'tkellem/irc_message'
|
10
12
|
require 'tkellem/tkellem_bot'
|
@@ -44,10 +46,23 @@ class Backlog
|
|
44
46
|
true
|
45
47
|
end
|
46
48
|
|
47
|
-
|
48
49
|
#### IMPL
|
49
50
|
|
50
|
-
|
51
|
+
def self.geoip(conn)
|
52
|
+
if !defined?(@geoip)
|
53
|
+
begin
|
54
|
+
@geoip = GeoIP.new('/usr/share/GeoIP/GeoIPCity.dat')
|
55
|
+
rescue Errno::ENOENT
|
56
|
+
@geoip = nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
geoip_info = @geoip && @geoip.country(Socket.unpack_sockaddr_in(conn.get_peername).last)
|
60
|
+
tz = geoip_info.respond_to?(:timezone) && geoip_info.timezone && ActiveSupport::TimeZone[geoip_info.timezone]
|
61
|
+
country = geoip_info.respond_to?(:country_code2) && geoip_info.country_code2
|
62
|
+
[tz, country]
|
63
|
+
end
|
64
|
+
|
65
|
+
class Device < Struct.new(:network_user, :device_name, :positions, :time_zone, :country)
|
51
66
|
def initialize(*a)
|
52
67
|
super
|
53
68
|
self.positions = {}
|
@@ -117,6 +132,9 @@ class Backlog
|
|
117
132
|
|
118
133
|
def client_connected(conn)
|
119
134
|
device = get_device(conn)
|
135
|
+
tz, country = self.class.geoip(conn)
|
136
|
+
device.time_zone = tz || device.time_zone
|
137
|
+
device.country = country || device.country
|
120
138
|
behind = all_existing_ctxs.select do |ctx_name|
|
121
139
|
eof = stream_size(ctx_name)
|
122
140
|
# default to the end of file, rather than the beginning, for new devices
|
@@ -142,6 +160,10 @@ class Backlog
|
|
142
160
|
"backlog:#{@bouncer.log_name}"
|
143
161
|
end
|
144
162
|
|
163
|
+
def now_timestamp
|
164
|
+
Time.now.utc.iso8601
|
165
|
+
end
|
166
|
+
|
145
167
|
def server_msg(msg)
|
146
168
|
case msg.command
|
147
169
|
when /3\d\d/, 'JOIN', 'PART'
|
@@ -154,7 +176,7 @@ class Backlog
|
|
154
176
|
# incoming pm, fake ctx to be the sender's nick
|
155
177
|
ctx = msg.prefix.split(/[!~@]/, 2).first
|
156
178
|
end
|
157
|
-
write_msg(ctx,
|
179
|
+
write_msg(ctx, "#{now_timestamp} < #{'* ' if msg.action?}#{msg.prefix}: #{msg.args.last}")
|
158
180
|
end
|
159
181
|
end
|
160
182
|
|
@@ -163,7 +185,7 @@ class Backlog
|
|
163
185
|
when 'PRIVMSG'
|
164
186
|
return if msg.ctcp? && !msg.action?
|
165
187
|
ctx = msg.args.first
|
166
|
-
write_msg(ctx,
|
188
|
+
write_msg(ctx, "#{now_timestamp} > #{'* ' if msg.action?}#{msg.args.last}")
|
167
189
|
end
|
168
190
|
end
|
169
191
|
|
@@ -179,32 +201,34 @@ class Backlog
|
|
179
201
|
start_pos = device.pos(ctx_name)
|
180
202
|
get_stream(ctx_name, true) do |stream|
|
181
203
|
stream.seek(start_pos)
|
182
|
-
send_backlog(conn, ctx_name, stream)
|
204
|
+
send_backlog(conn, ctx_name, stream, device.time_zone)
|
183
205
|
device.update_pos(ctx_name, stream.pos)
|
184
206
|
end
|
185
207
|
end
|
186
208
|
end
|
187
209
|
|
188
210
|
def send_backlog_since(conn, start_time, contexts)
|
189
|
-
debug "scanning for backlog from #{start_time.
|
211
|
+
debug "scanning for backlog from #{start_time.iso8601}"
|
190
212
|
contexts.each do |ctx_name|
|
191
213
|
get_stream(ctx_name, true) do |stream|
|
192
214
|
last_line_len = 0
|
193
215
|
BackwardsFileReader.scan(stream) do |line|
|
194
216
|
# remember this last line length so we can scan past it
|
195
217
|
last_line_len = line.length
|
196
|
-
timestamp = Time.parse(line[0,
|
218
|
+
timestamp = Time.parse(line[0,20]) rescue nil
|
197
219
|
!timestamp || timestamp >= start_time
|
198
220
|
end
|
199
221
|
stream.seek(last_line_len, IO::SEEK_CUR)
|
200
|
-
send_backlog(conn, ctx_name, stream)
|
222
|
+
send_backlog(conn, ctx_name, stream, get_device(conn).time_zone)
|
201
223
|
end
|
202
224
|
end
|
203
225
|
end
|
204
226
|
|
205
|
-
def send_backlog(conn, ctx_name, stream)
|
227
|
+
def send_backlog(conn, ctx_name, stream, time_zone)
|
206
228
|
while line = stream.gets
|
207
229
|
timestamp, msg = self.class.parse_line(line, ctx_name)
|
230
|
+
timestamp = timestamp.in_time_zone(time_zone) if timestamp && time_zone
|
231
|
+
timestamp = timestamp.localtime if timestamp && !time_zone
|
208
232
|
next unless msg
|
209
233
|
privmsg = msg.args.first[0] != '#'[0]
|
210
234
|
if msg.prefix
|
@@ -233,15 +257,18 @@ class Backlog
|
|
233
257
|
end
|
234
258
|
|
235
259
|
def self.parse_line(line, ctx_name)
|
236
|
-
timestamp = Time.parse(line[0,
|
260
|
+
timestamp = Time.parse(line[0, 20])
|
261
|
+
# older log lines have a timestamp that is one character shorter, which is
|
262
|
+
# why the regular expressions below optionally allow a space in front of
|
263
|
+
# the directional < >
|
237
264
|
case line[20..-1]
|
238
|
-
when %r{
|
265
|
+
when %r{^ ?> (\* )?(.+)$}
|
239
266
|
msg = IrcMessage.new(nil, 'PRIVMSG', [ctx_name, $2])
|
240
267
|
if $1 == '* '
|
241
268
|
msg.ctcp = 'ACTION'
|
242
269
|
end
|
243
270
|
return timestamp, msg
|
244
|
-
when %r{
|
271
|
+
when %r{^ ?< (\* )?([^ ]+): (.+)$}
|
245
272
|
msg = IrcMessage.new($2, 'PRIVMSG', [ctx_name, $3])
|
246
273
|
if $1 == '* '
|
247
274
|
msg.ctcp = 'ACTION'
|
@@ -275,4 +302,44 @@ class BacklogCommand < TkellemBot::Command
|
|
275
302
|
end
|
276
303
|
end
|
277
304
|
|
305
|
+
class TimezoneCommand < TkellemBot::Command
|
306
|
+
register 'timezone'
|
307
|
+
|
308
|
+
def self.admin_only?
|
309
|
+
false
|
310
|
+
end
|
311
|
+
|
312
|
+
def execute
|
313
|
+
backlog = Backlog.get_instance(bouncer)
|
314
|
+
device = backlog.get_device(conn)
|
315
|
+
|
316
|
+
if !args.empty?
|
317
|
+
arg = args.join(' ')
|
318
|
+
tz = ActiveSupport::TimeZone[arg]
|
319
|
+
if !tz
|
320
|
+
conn.say_as_tkellem "Unknown time zone '#{arg}'; please use an IANA time zone"
|
321
|
+
return
|
322
|
+
end
|
323
|
+
device.time_zone = tz
|
324
|
+
end
|
325
|
+
|
326
|
+
if !device.time_zone
|
327
|
+
conn.say_as_tkellem "<time zone not set; using #{Time.now.zone}>"
|
328
|
+
return
|
329
|
+
end
|
330
|
+
|
331
|
+
# try to find a friendlier name to show the user
|
332
|
+
begin
|
333
|
+
country_zone = TZInfo::Country.get(device.country).zone_info.detect { |z| z.identifier == device.time_zone.name }
|
334
|
+
if country_zone
|
335
|
+
conn.say_as_tkellem country_zone.description_or_friendly_identifier
|
336
|
+
return
|
337
|
+
end
|
338
|
+
rescue TZInfo::InvalidCountryCode
|
339
|
+
end
|
340
|
+
|
341
|
+
conn.say_as_tkellem device.time_zone.tzinfo.friendly_identifier
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
278
345
|
end
|
data/lib/tkellem/tkellem_bot.rb
CHANGED
data/lib/tkellem/version.rb
CHANGED
data/spec/irc_message_spec.rb
CHANGED
@@ -42,10 +42,10 @@ end
|
|
42
42
|
describe IrcMessage, "#with_timestamp" do
|
43
43
|
it "should prefix a timestamp to the last arg" do
|
44
44
|
line = IrcMessage.parse(":some_long_prefix COMMAND first second :long arg here")
|
45
|
-
timestamp = Time.parse("
|
45
|
+
timestamp = Time.parse("2001-11-29T19:33:20")
|
46
46
|
ts_line = line.with_timestamp(timestamp)
|
47
47
|
ts_line.should be_a(IrcMessage)
|
48
|
-
ts_line.to_s.should == ":some_long_prefix COMMAND first second :2001-11-29
|
48
|
+
ts_line.to_s.should == ":some_long_prefix COMMAND first second :2001-11-29 19:33:20> long arg here"
|
49
49
|
end
|
50
50
|
|
51
51
|
it "should not prefix the date if the message is < 24 hours old" do
|
@@ -18,5 +18,27 @@ describe Backlog do
|
|
18
18
|
cmp("dude!~dude@12:34:56")
|
19
19
|
cmp("dude!~dude@12:34:56", "hey dude: test")
|
20
20
|
end
|
21
|
+
|
22
|
+
context "timestamps" do
|
23
|
+
it "should parse legacy timestamps" do
|
24
|
+
Time.use_zone("US/Mountain") do
|
25
|
+
timestamp, msg = Backlog.parse_line(%{10-07-2013 10:10:36 < test: hello}, '#testroom')
|
26
|
+
timestamp.should == Time.zone.parse("10-07-2013 10:10:36")
|
27
|
+
msg.prefix.should == 'test'
|
28
|
+
msg.command.should == 'PRIVMSG'
|
29
|
+
msg.args.should == ['#testroom', 'hello']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should parse utc timestamps" do
|
34
|
+
Time.use_zone("US/Mountain") do
|
35
|
+
timestamp, msg = Backlog.parse_line(%{2013-07-26T23:06:10Z < test: hello}, '#testroom')
|
36
|
+
timestamp.should == Time.parse("2013-07-26T23:06:10Z")
|
37
|
+
msg.prefix.should == 'test'
|
38
|
+
msg.command.should == 'PRIVMSG'
|
39
|
+
msg.args.should == ['#testroom', 'hello']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
21
43
|
end
|
22
44
|
end
|
data/tkellem.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tkellem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-10-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: eventmachine
|
@@ -75,6 +75,38 @@ dependencies:
|
|
75
75
|
- - ~>
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: 0.1.1
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: tzinfo
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: geoip
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.3.2
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.3.2
|
78
110
|
description:
|
79
111
|
email:
|
80
112
|
- brian@codekitchen.net
|
@@ -152,18 +184,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
152
184
|
- - ! '>='
|
153
185
|
- !ruby/object:Gem::Version
|
154
186
|
version: '0'
|
155
|
-
segments:
|
156
|
-
- 0
|
157
|
-
hash: -2551777789450779146
|
158
187
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
188
|
none: false
|
160
189
|
requirements:
|
161
190
|
- - ! '>='
|
162
191
|
- !ruby/object:Gem::Version
|
163
192
|
version: '0'
|
164
|
-
segments:
|
165
|
-
- 0
|
166
|
-
hash: -2551777789450779146
|
167
193
|
requirements: []
|
168
194
|
rubyforge_project:
|
169
195
|
rubygems_version: 1.8.23
|
@@ -177,3 +203,4 @@ test_files:
|
|
177
203
|
- spec/irc_server_spec.rb
|
178
204
|
- spec/plugins/backlog_spec.rb
|
179
205
|
- spec/spec_helper.rb
|
206
|
+
has_rdoc:
|