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 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: