tkellem 0.9.3 → 0.9.4
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.
- 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:
|