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 CHANGED
@@ -13,4 +13,5 @@ end
13
13
  group :development do
14
14
  gem 'guard-rspec'
15
15
  gem 'rspec-nc'
16
+ gem 'debugger'
16
17
  end
data/Gemfile.lock CHANGED
@@ -1,52 +1,55 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tkellem (0.9.3)
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
- actionpack (4.0.0.rc2)
14
- activesupport (= 4.0.0.rc2)
15
+ activemodel (4.0.1.rc3)
16
+ activesupport (= 4.0.1.rc3)
15
17
  builder (~> 3.1.0)
16
- erubis (~> 2.7.0)
17
- rack (~> 1.5.2)
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.0.rc2)
21
+ activesupport (= 4.0.1.rc3)
26
22
  arel (~> 4.0.0)
27
23
  activerecord-deprecated_finders (1.0.3)
28
- activesupport (4.0.0.rc2)
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.0)
35
- atomic (1.1.9)
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.4)
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
- rack (1.5.2)
77
- rack-test (0.6.2)
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.7)
104
+ sqlite3 (1.3.8)
111
105
  terminal-notifier (1.4.2)
112
106
  thor (0.18.1)
113
- thread_safe (0.1.0)
107
+ thread_safe (0.1.3)
114
108
  atomic
115
- tzinfo (0.3.37)
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)
@@ -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
 
@@ -1,4 +1,7 @@
1
1
  # encoding: utf-8
2
+
3
+ require 'active_support/core_ext/time'
4
+
2
5
  module Tkellem
3
6
 
4
7
  class IrcMessage < Struct.new(:prefix, :command, :args, :ctcp)
@@ -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
- class Device < Struct.new(:network_user, :device_name, :positions)
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, Time.now.strftime("%d-%m-%Y %H:%M:%S") + " < #{'* ' if msg.action?}#{msg.prefix}: #{msg.args.last}")
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, Time.now.strftime("%d-%m-%Y %H:%M:%S") + " > #{'* ' if msg.action?}#{msg.args.last}")
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.inspect}"
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,19]) rescue nil
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, 19])
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
@@ -9,7 +9,7 @@ class TkellemBot
9
9
  # an admin
10
10
  def self.run_command(line, bouncer, conn, &block)
11
11
  args = Shellwords.shellwords(line)
12
- command_name = args.shift.upcase
12
+ command_name = args.shift.try(:upcase)
13
13
  command = commands[command_name]
14
14
 
15
15
  unless command
@@ -95,7 +95,7 @@ class TkellemServer
95
95
  @bouncers[key] = Bouncer.new(network_user)
96
96
  end
97
97
 
98
- def stop_bouncer(obj)
98
+ def stop_bouncer(network_user)
99
99
  key = bouncers_key(network_user)
100
100
  bouncer = @bouncers.delete(key)
101
101
  if bouncer
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Tkellem
3
- VERSION = "0.9.3"
3
+ VERSION = "0.9.4"
4
4
  end
@@ -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("Thu Nov 29 14:33:20 2001")
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 14:33:20> long arg here"
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
@@ -21,4 +21,6 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency "activerecord", "~> 4.0.0.rc2"
22
22
  s.add_dependency "sqlite3", "~> 1.3.3"
23
23
  s.add_dependency "rails-observers", "~> 0.1.1"
24
+ s.add_dependency "tzinfo"
25
+ s.add_dependency "geoip", "~> 1.3.2"
24
26
  end
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.3
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-07-12 00:00:00.000000000 Z
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: