net-irc 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/examples/nig.rb CHANGED
@@ -63,7 +63,7 @@ class NowaIrcGateway < TwitterIrcGateway
63
63
  end
64
64
 
65
65
  def api_base
66
- URI("http://api.nowa.jp/")
66
+ URI("https://api.nowa.jp/")
67
67
  end
68
68
 
69
69
  def api_source
@@ -83,6 +83,7 @@ if __FILE__ == $0
83
83
  :host => "localhost",
84
84
  :log => nil,
85
85
  :debug => false,
86
+ :foreground => false,
86
87
  }
87
88
 
88
89
  OptionParser.new do |parser|
@@ -112,6 +113,11 @@ if __FILE__ == $0
112
113
  opts[:debug] = true
113
114
  end
114
115
 
116
+ on("-f", "--foreground", "run foreground") do |foreground|
117
+ opts[:log] = $stdout
118
+ opts[:foreground] = true
119
+ end
120
+
115
121
  parse!(ARGV)
116
122
  end
117
123
  end
@@ -119,14 +125,14 @@ if __FILE__ == $0
119
125
  opts[:logger] = Logger.new(opts[:log], "daily")
120
126
  opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
121
127
 
122
- def daemonize(debug=false)
123
- return yield if $DEBUG || debug
128
+ def daemonize(foreground=false)
129
+ trap("SIGINT") { exit! 0 }
130
+ trap("SIGTERM") { exit! 0 }
131
+ trap("SIGHUP") { exit! 0 }
132
+ return yield if $DEBUG || foreground
124
133
  Process.fork do
125
134
  Process.setsid
126
135
  Dir.chdir "/"
127
- trap("SIGINT") { exit! 0 }
128
- trap("SIGTERM") { exit! 0 }
129
- trap("SIGHUP") { exit! 0 }
130
136
  File.open("/dev/null") {|f|
131
137
  STDIN.reopen f
132
138
  STDOUT.reopen f
@@ -137,7 +143,7 @@ if __FILE__ == $0
137
143
  exit! 0
138
144
  end
139
145
 
140
- daemonize(opts[:debug]) do
146
+ daemonize(opts[:debug] || opts[:foreground]) do
141
147
  Net::IRC::Server.new(opts[:host], opts[:port], NowaIrcGateway, opts).start
142
148
  end
143
149
  end
data/examples/sig.rb ADDED
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env ruby
2
+ =begin
3
+ # sig.rb
4
+
5
+ ServerLog IRC Gateway
6
+
7
+ # Usage
8
+
9
+ * Connect.
10
+ * Join a channel (you can name it as you like)
11
+ * Set topic "filename regexp"
12
+ * You will see the log at the channel only matching the regexp.
13
+
14
+ =end
15
+
16
+ $LOAD_PATH << "lib"
17
+ $LOAD_PATH << "../lib"
18
+
19
+ $KCODE = "u" # json use this
20
+
21
+ require "rubygems"
22
+ require "net/irc"
23
+ require "logger"
24
+ require "pathname"
25
+ require "yaml"
26
+
27
+ class ServerLogIrcGateway < Net::IRC::Server::Session
28
+ def server_name
29
+ "serverlog"
30
+ end
31
+
32
+ def server_version
33
+ "0.0.0"
34
+ end
35
+
36
+
37
+ def initialize(*args)
38
+ super
39
+ @channels = {}
40
+ @config = Pathname.new(ENV["HOME"]) + ".sig"
41
+ end
42
+
43
+ def on_disconnected
44
+ @channels.each do |chan, info|
45
+ begin
46
+ info[:observer].kill if info[:observer]
47
+ rescue
48
+ end
49
+ end
50
+ end
51
+
52
+ def on_user(m)
53
+ super
54
+ @real, *@opts = @real.split(/\s+/)
55
+ @opts ||= []
56
+ end
57
+
58
+ def on_join(m)
59
+ channels = m.params.first.split(/,/)
60
+ channels.each do |channel|
61
+ @channels[channel] = {
62
+ :topic => "",
63
+ :observer => nil,
64
+ } unless @channels.key?(channel)
65
+ post @prefix, JOIN, m.params.first
66
+ post nil, RPL_NAMREPLY, @prefix.nick, "=", channel, "@#{@prefix.nick}"
67
+ post nil, RPL_ENDOFNAMES, @prefix.nick, channel, "End of NAMES list"
68
+ end
69
+ end
70
+
71
+ def on_topic(m)
72
+ channel, topic, = m.params
73
+ p m.params
74
+ if @channels.key?(channel)
75
+ post @prefix, TOPIC, channel, topic
76
+ @channels[channel][:topic] = topic
77
+ create_observer(channel)
78
+ end
79
+ end
80
+
81
+ def create_observer(channel)
82
+ return unless @channels.key?(channel)
83
+ info = @channels[channel]
84
+ @log.debug "create_observer<#{channel}>#{info.inspect}"
85
+ begin
86
+ info[:observer].kill if info[:observer]
87
+ rescue
88
+ end
89
+ info[:observer] = Thread.start(channel, info) do |chan, i|
90
+ file, reg, = i[:topic].split(/\s+/)
91
+ name = File.basename(file)
92
+ grep = Regexp.new(reg.to_s)
93
+ @log.info "#{file} / grep => #{grep.inspect}"
94
+ File.open(file) do |f|
95
+ size = File.size(f)
96
+ f.seek size
97
+ loop do
98
+ sleep 1
99
+ nsize = File.size(f)
100
+ if nsize > size
101
+ @log.debug "follow up log"
102
+ l = f.gets
103
+ if grep === l
104
+ post name, PRIVMSG, chan, l
105
+ end
106
+ end
107
+ size = nsize
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ if __FILE__ == $0
115
+ require "optparse"
116
+
117
+ opts = {
118
+ :port => 16700,
119
+ :host => "localhost",
120
+ :log => nil,
121
+ :debug => false,
122
+ :foreground => false,
123
+ }
124
+
125
+ OptionParser.new do |parser|
126
+ parser.instance_eval do
127
+ self.banner = <<-EOB.gsub(/^\t+/, "")
128
+ Usage: #{$0} [opts]
129
+
130
+ EOB
131
+
132
+ separator ""
133
+
134
+ separator "Options:"
135
+ on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
136
+ opts[:port] = port
137
+ end
138
+
139
+ on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
140
+ opts[:host] = host
141
+ end
142
+
143
+ on("-l", "--log LOG", "log file") do |log|
144
+ opts[:log] = log
145
+ end
146
+
147
+ on("--debug", "Enable debug mode") do |debug|
148
+ opts[:log] = $stdout
149
+ opts[:debug] = true
150
+ end
151
+
152
+ on("-f", "--foreground", "run foreground") do |foreground|
153
+ opts[:log] = $stdout
154
+ opts[:foreground] = true
155
+ end
156
+
157
+ parse!(ARGV)
158
+ end
159
+ end
160
+
161
+ opts[:logger] = Logger.new(opts[:log], "daily")
162
+ opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
163
+
164
+ def daemonize(foreground=false)
165
+ trap("SIGINT") { exit! 0 }
166
+ trap("SIGTERM") { exit! 0 }
167
+ trap("SIGHUP") { exit! 0 }
168
+ return yield if $DEBUG || foreground
169
+ Process.fork do
170
+ Process.setsid
171
+ Dir.chdir "/"
172
+ File.open("/dev/null") {|f|
173
+ STDIN.reopen f
174
+ STDOUT.reopen f
175
+ STDERR.reopen f
176
+ }
177
+ yield
178
+ end
179
+ exit! 0
180
+ end
181
+
182
+ daemonize(opts[:debug] || opts[:foreground]) do
183
+ Net::IRC::Server.new(opts[:host], opts[:port], ServerLogIrcGateway, opts).start
184
+ end
185
+ end
186
+
data/examples/tig.rb CHANGED
@@ -23,7 +23,7 @@ Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
23
23
  twitter {
24
24
  host: localhost
25
25
  port: 16668
26
- name: username@example.com athack
26
+ name: username@example.com athack jabber=username@example.com:jabberpasswd
27
27
  password: password on Twitter
28
28
  in-encoding: utf8
29
29
  out-encoding: utf8
@@ -65,8 +65,9 @@ $LOAD_PATH << "../lib"
65
65
  $KCODE = "u" # json use this
66
66
 
67
67
  require "rubygems"
68
- require "net/http"
69
68
  require "net/irc"
69
+ require "net/http"
70
+ require "net/https"
70
71
  require "uri"
71
72
  require "json"
72
73
  require "socket"
@@ -74,7 +75,6 @@ require "time"
74
75
  require "logger"
75
76
  require "yaml"
76
77
  require "pathname"
77
- require "digest/md5"
78
78
  require "cgi"
79
79
 
80
80
  Net::HTTP.version_1_2
@@ -108,10 +108,11 @@ class TwitterIrcGateway < Net::IRC::Server::Session
108
108
 
109
109
  def initialize(*args)
110
110
  super
111
- @groups = {}
112
- @channels = [] # join channels (groups)
111
+ @groups = {}
112
+ @channels = [] # joined channels (groups)
113
113
  @user_agent = "#{self.class}/#{server_version} (tig.rb)"
114
- @config = Pathname.new(ENV["HOME"]) + ".tig"
114
+ @config = Pathname.new(ENV["HOME"]) + ".tig"
115
+ @map = nil
115
116
  load_config
116
117
  end
117
118
 
@@ -120,8 +121,9 @@ class TwitterIrcGateway < Net::IRC::Server::Session
120
121
  post @prefix, JOIN, main_channel
121
122
  post server_name, MODE, main_channel, "+o", @prefix.nick
122
123
 
123
- @real, *@opts = @real.split(/\s+/)
124
+ @real, *@opts = @opts.name || @real.split(/\s+/)
124
125
  @opts ||= []
126
+ @tmap = TypableMap.new
125
127
 
126
128
  jabber = @opts.find {|i| i =~ /^jabber=(\S+?):(\S+)/ }
127
129
  if jabber
@@ -145,7 +147,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
145
147
  @log.info "Client Options: #{@opts.inspect}"
146
148
 
147
149
  @timeline = []
148
- Thread.start do
150
+ @check_friends_thread = Thread.start do
149
151
  loop do
150
152
  begin
151
153
  check_friends
@@ -157,14 +159,14 @@ class TwitterIrcGateway < Net::IRC::Server::Session
157
159
  @log.error "\t#{l}"
158
160
  end
159
161
  end
160
- sleep 10 * 60
162
+ sleep 10 * 60 # 6 times/hour
161
163
  end
162
164
  end
163
165
  sleep 3
164
166
 
165
167
  return if jabber
166
168
 
167
- Thread.start do
169
+ @check_timeline_thread = Thread.start do
168
170
  loop do
169
171
  begin
170
172
  check_timeline
@@ -177,18 +179,31 @@ class TwitterIrcGateway < Net::IRC::Server::Session
177
179
  @log.error "\t#{l}"
178
180
  end
179
181
  end
180
- sleep 90
182
+ sleep 180 # 20 times/hour
181
183
  end
182
184
  end
183
185
  end
184
186
 
187
+ def on_disconnected
188
+ @check_friends_thread.kill rescue nil
189
+ @check_timeline_thread.kill rescue nil
190
+ @im_thread.kill rescue nil
191
+ @im.disconnect rescue nil
192
+ end
193
+
185
194
  def on_privmsg(m)
195
+ return on_ctcp(m[0], ctcp_decoding(m[1])) if m.ctcp?
186
196
  retry_count = 3
187
197
  ret = nil
188
198
  target, message = *m.params
189
199
  begin
190
200
  if target =~ /^#/
191
- ret = api("statuses/update", {"status" => message})
201
+ if @im && @im.connected? # in jabber mode, using jabber post
202
+ ret = @im.deliver(jabber_bot_id, message)
203
+ post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(message)
204
+ else
205
+ ret = api("statuses/update", {"status" => message})
206
+ end
192
207
  else
193
208
  # direct message
194
209
  ret = api("direct_messages/new", {"user" => target, "text" => message})
@@ -207,6 +222,34 @@ class TwitterIrcGateway < Net::IRC::Server::Session
207
222
  end
208
223
  end
209
224
 
225
+ def on_ctcp(target, message)
226
+ _, command, *args = message.split(/\s+/)
227
+ case command
228
+ when "list"
229
+ nick = args[0]
230
+ @log.debug([ nick, message ])
231
+ res = api('statuses/user_timeline', { 'id' => nick }).reverse_each do |s|
232
+ @log.debug(s)
233
+ post nick, NOTICE, main_channel, "#{generate_status_message(s)}"
234
+ end
235
+
236
+ unless res
237
+ post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
238
+ end
239
+ when "fav"
240
+ tid = args[0]
241
+ id = @tmap[tid]
242
+ if id
243
+ res = api("favorites/create/#{id}", {})
244
+ post nil, NOTICE, main_channel, "Fav: #{res["screen_name"]}: #{res["text"]}"
245
+ else
246
+ post nil, NOTICE, main_channel, "No such id #{tid}"
247
+ end
248
+ end
249
+ rescue ApiFailed => e
250
+ log e.inspect
251
+ end
252
+
210
253
  def on_whois(m)
211
254
  nick = m.params[0]
212
255
  f = (@friends || []).find {|i| i["screen_name"] == nick }
@@ -298,32 +341,25 @@ class TwitterIrcGateway < Net::IRC::Server::Session
298
341
 
299
342
  private
300
343
  def check_timeline
301
- first = true unless @prev_time
302
- @prev_time = Time.at(0) if first
303
- api("statuses/friends_timeline", {"since" => [@prev_time.httpdate]}).reverse_each do |s|
304
- nick = s["user"]["screen_name"]
305
- mesg = s["text"]
306
- # display photo url(wassr only)
307
- if s.has_key?('photo_url')
308
- mesg += " #{s['photo_url']}"
344
+ @prev_time ||= Time.at(0)
345
+ api("statuses/friends_timeline", {"since" => @prev_time.httpdate}).reverse_each do |s|
346
+ id = s["id"] || s["rid"]
347
+ next if id.nil? || @timeline.include?(id)
348
+ @timeline << id
349
+ nick = s["user_login_id"] || s["user"]["screen_name"] # it may be better to use user_login_id in Wassr
350
+ mesg = generate_status_message(s)
351
+
352
+ tid = @tmap.push(id)
353
+
354
+ @log.debug [id, nick, mesg]
355
+ if nick == @nick # 自分のときは topic に
356
+ post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(mesg)
357
+ else
358
+ message(nick, main_channel, "%s [%s]" % [mesg, tid])
309
359
  end
310
- # time = Time.parse(s["created_at"]) rescue Time.now
311
- m = { "&quot;" => "\"", "&lt;"=> "<", "&gt;"=> ">", "&amp;"=> "&", "\n" => " "}
312
- mesg.gsub!(/(#{m.keys.join("|")})/) { m[$1] }
313
-
314
- digest = Digest::MD5.hexdigest("#{nick}::#{mesg}")
315
- unless @timeline.include?(digest)
316
- @timeline << digest
317
- @log.debug [nick, mesg]
318
- if nick == @nick # 自分のときは topic に
319
- post nick, TOPIC, main_channel, mesg
320
- else
321
- message(nick, main_channel, mesg)
322
- end
323
- @groups.each do |channel,members|
324
- if members.include?(nick)
325
- message(nick, channel, mesg)
326
- end
360
+ @groups.each do |channel, members|
361
+ if members.include?(nick)
362
+ message(nick, channel, "%s [%s]" % [mesg, tid])
327
363
  end
328
364
  end
329
365
  end
@@ -332,10 +368,34 @@ class TwitterIrcGateway < Net::IRC::Server::Session
332
368
  @prev_time = Time.now
333
369
  end
334
370
 
371
+ def generate_status_message(status)
372
+ s = status
373
+ mesg = s["text"]
374
+ @log.debug(mesg)
375
+
376
+ # added @user in no use @user reply message ( Wassr only )
377
+ if s.has_key?('reply_status_url') and s['reply_status_url'] and s['text'] !~ /^@.*/ and %r{([^/]+)/statuses/[^/]+}.match(s['reply_status_url'])
378
+ reply_user_id = $1
379
+ mesg = "@#{reply_user_id} #{mesg}"
380
+ end
381
+ # display area name(Wassr only)
382
+ if s.has_key?('areaname') and s["areaname"]
383
+ mesg += " L: #{s['areaname']}"
384
+ end
385
+ # display photo url(Wassr only)
386
+ if s.has_key?('photo_url') and s["photo_url"]
387
+ mesg += " #{s['photo_url']}"
388
+ end
389
+
390
+ # time = Time.parse(s["created_at"]) rescue Time.now
391
+ m = { "&quot;" => "\"", "&lt;"=> "<", "&gt;"=> ">", "&amp;"=> "&", "\n" => " "}
392
+ mesg.gsub!(/(#{m.keys.join("|")})/) { m[$1] }
393
+ mesg
394
+ end
395
+
335
396
  def check_direct_messages
336
- first = true unless @prev_time_d
337
- @prev_time_d = Time.now if first
338
- api("direct_messages", {"since" => [@prev_time_d.httpdate] }).reverse_each do |s|
397
+ @prev_time_d ||= Time.now
398
+ api("direct_messages", {"since" => @prev_time_d.httpdate}).reverse_each do |s|
339
399
  nick = s["sender_screen_name"]
340
400
  mesg = s["text"]
341
401
  time = Time.parse(s["created_at"])
@@ -356,6 +416,10 @@ class TwitterIrcGateway < Net::IRC::Server::Session
356
416
  else
357
417
  prv_friends = @friends.map {|i| i["screen_name"] }
358
418
  now_friends = friends.map {|i| i["screen_name"] }
419
+
420
+ # twitter api bug?
421
+ return if !first && (now_friends.length - prv_friends.length).abs > 10
422
+
359
423
  (now_friends - prv_friends).each do |join|
360
424
  join = "@#{join}" if @opts.include?("athack")
361
425
  post "#{join}!#{join}@#{api_base.host}", JOIN, main_channel
@@ -370,16 +434,16 @@ class TwitterIrcGateway < Net::IRC::Server::Session
370
434
 
371
435
  def start_jabber(jid, pass)
372
436
  @log.info "Logging-in with #{jid} -> jabber_bot_id: #{jabber_bot_id}"
373
- im = Jabber::Simple.new(jid, pass)
374
- im.add(jabber_bot_id)
375
- Thread.start do
437
+ @im = Jabber::Simple.new(jid, pass)
438
+ @im.add(jabber_bot_id)
439
+ @im_thread = Thread.start do
376
440
  loop do
377
441
  begin
378
- im.received_messages.each do |msg|
442
+ @im.received_messages.each do |msg|
379
443
  @log.debug [msg.from, msg.body]
380
444
  if msg.from.strip == jabber_bot_id
381
- # twitter -> 'id: msg'
382
- # wassr -> 'nick(id): msg'
445
+ # Twitter -> 'id: msg'
446
+ # Wassr -> 'nick(id): msg'
383
447
  body = msg.body.sub(/^(.+?)(?:\((.+?)\))?: /, "")
384
448
  if Regexp.last_match
385
449
  nick, id = Regexp.last_match.captures
@@ -402,7 +466,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
402
466
  def save_config
403
467
  config = {
404
468
  :channels => @channels,
405
- :groups => @groups,
469
+ :groups => @groups,
406
470
  }
407
471
  @config.open("w") do |f|
408
472
  YAML.dump(config, f)
@@ -419,45 +483,61 @@ class TwitterIrcGateway < Net::IRC::Server::Session
419
483
  end
420
484
 
421
485
  def api(path, q={})
422
- ret = {}
423
- path = path.sub(%r{^/*}, '/') << '.json'
424
- q["source"] = api_source
486
+ ret = {}
487
+ header = {
488
+ "User-Agent" => @user_agent,
489
+ "Authorization" => "Basic " + ["#{@real}:#{@pass}"].pack("m"),
490
+ "X-Twitter-Client" => api_source,
491
+ "X-Twitter-Client-Version" => server_version,
492
+ "X-Twitter-Client-URL" => "http://coderepos.org/share/browser/lang/ruby/misc/tig.rb",
493
+ }
494
+ header["If-Modified-Since"] = q["since"] if q.key?("since")
495
+
496
+ q["source"] ||= api_source
425
497
  q = q.inject([]) {|r,(k,v)| v.inject(r) {|r,i| r << "#{k}=#{URI.escape(i, /[^-.!~*'()\w]/n)}" } }.join("&")
498
+
426
499
  uri = api_base.dup
427
- uri.path = path
500
+ uri.path = path.sub(%r{^/*}, "/") << ".json"
428
501
  uri.query = q
502
+
429
503
  @log.debug uri.inspect
430
- Net::HTTP.start(uri.host, uri.port) do |http|
431
- header = {
432
- 'Authorization' => "Basic " + ["#{@real}:#{@pass}"].pack("m"),
433
- 'User-Agent' => @user_agent,
434
- }
435
- case path
504
+ http = Net::HTTP.new(uri.host, uri.port)
505
+ if uri.scheme == "https"
506
+ http.use_ssl = true
507
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # FIXME
508
+ end
509
+ http.start do
510
+ case uri.path
436
511
  when "/statuses/update.json", "/direct_messages/new.json"
437
512
  ret = http.post(uri.request_uri, q, header)
438
513
  else
439
514
  ret = http.get(uri.request_uri, header)
440
515
  end
441
516
  end
442
- @log.debug ret.inspect
443
- case ret.code
444
- when "200"
445
- JSON.parse(ret.body.gsub(/'(y(?:es)?|n(?:o)?|true|false|null)'/, '"\1"'))
446
- when "304"
517
+
518
+ case ret
519
+ when Net::HTTPOK # 200
520
+ ret = JSON.parse(ret.body.gsub(/'(y(?:es)?|no?|true|false|null)'/, '"\1"'))
521
+ raise ApiFailed, "Server Returned Error: #{ret["error"]}" if ret.kind_of?(Hash) && ret["error"]
522
+ ret
523
+ when Net::HTTPNotModified # 304
447
524
  []
525
+ when Net::HTTPBadRequest # 400
526
+ # exceeded the rate limitation
527
+ raise ApiFailed, "#{ret.code}: #{ret["error"]}"
448
528
  else
449
- raise ApiFailed, "Server Returned #{ret.code}"
529
+ raise ApiFailed, "Server Returned #{ret.code} #{ret.message}"
450
530
  end
451
531
  rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
452
532
  raise ApiFailed, e.inspect
453
533
  end
454
534
 
455
535
  def message(sender, target, str)
456
- # str.gsub!(/&#(x)?([0-9a-f]+);/i) do |m|
457
- # [$1 ? $2.hex : $2.to_i].pack("U")
458
- # end
459
- str = untinyurl(str)
460
- sender = "#{sender}!#{sender}@#{api_base.host}"
536
+ # str.gsub!(/&#(x)?([0-9a-f]+);/i) do
537
+ # [$1 ? $2.hex : $2.to_i].pack("U")
538
+ # end
539
+ str = untinyurl(str)
540
+ sender = "#{sender}!#{sender}@#{api_base.host}"
461
541
  post sender, PRIVMSG, target, str
462
542
  end
463
543
 
@@ -469,17 +549,58 @@ class TwitterIrcGateway < Net::IRC::Server::Session
469
549
  def untinyurl(text)
470
550
  text.gsub(%r|http://(preview\.)?tinyurl\.com/[0-9a-z=]+|i) {|m|
471
551
  uri = URI(m)
472
- uri.host = uri.host.sub($1, '') if $1
552
+ uri.host = uri.host.sub($1, "") if $1
473
553
  Net::HTTP.start(uri.host, uri.port) {|http|
474
554
  http.open_timeout = 3
475
555
  begin
476
- http.head(uri.request_uri, { 'User-Agent' => @user_agent })["Location"]
556
+ http.head(uri.request_uri, { "User-Agent" => @user_agent })["Location"] || m
477
557
  rescue Timeout::Error
478
558
  m
479
559
  end
480
560
  }
481
561
  }
482
562
  end
563
+
564
+ class TypableMap < Hash
565
+ Roma = "a i u e o k g s z t d n h b p m y r w j v l q".split(/ /).map {|k|
566
+ %w|a i u e o|.map {|v| "#{k}#{v}" }
567
+ }.flatten
568
+
569
+ def initialize(size=2)
570
+ @seq = Roma
571
+ @map = {}
572
+ @n = 0
573
+ @size = size
574
+ end
575
+
576
+ def generate(n)
577
+ ret = []
578
+ begin
579
+ n, r = n.divmod(@seq.size)
580
+ ret << @seq[r]
581
+ end while n > 0
582
+ ret.reverse.join
583
+ end
584
+
585
+ def push(obj)
586
+ id = generate(@n)
587
+ self[id] = obj
588
+ @n += 1
589
+ @n = @n % (@seq.size ** @size)
590
+ id
591
+ end
592
+ alias << push
593
+
594
+ def clear
595
+ @n = 0
596
+ super
597
+ end
598
+
599
+ private :[]=
600
+ undef update, merge, merge!, replace
601
+ end
602
+
603
+
483
604
  end
484
605
 
485
606
  if __FILE__ == $0
@@ -490,6 +611,7 @@ if __FILE__ == $0
490
611
  :host => "localhost",
491
612
  :log => nil,
492
613
  :debug => false,
614
+ :foreground => false,
493
615
  }
494
616
 
495
617
  OptionParser.new do |parser|
@@ -519,6 +641,15 @@ if __FILE__ == $0
519
641
  opts[:debug] = true
520
642
  end
521
643
 
644
+ on("-f", "--foreground", "run foreground") do |foreground|
645
+ opts[:log] = $stdout
646
+ opts[:foreground] = true
647
+ end
648
+
649
+ on("-n", "--name [user name or email address]") do |name|
650
+ opts[:name] = name
651
+ end
652
+
522
653
  parse!(ARGV)
523
654
  end
524
655
  end
@@ -526,14 +657,14 @@ if __FILE__ == $0
526
657
  opts[:logger] = Logger.new(opts[:log], "daily")
527
658
  opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
528
659
 
529
- def daemonize(debug=false)
530
- return yield if $DEBUG || debug
660
+ def daemonize(foreground=false)
661
+ trap("SIGINT") { exit! 0 }
662
+ trap("SIGTERM") { exit! 0 }
663
+ trap("SIGHUP") { exit! 0 }
664
+ return yield if $DEBUG || foreground
531
665
  Process.fork do
532
666
  Process.setsid
533
667
  Dir.chdir "/"
534
- trap("SIGINT") { exit! 0 }
535
- trap("SIGTERM") { exit! 0 }
536
- trap("SIGHUP") { exit! 0 }
537
668
  File.open("/dev/null") {|f|
538
669
  STDIN.reopen f
539
670
  STDOUT.reopen f
@@ -544,8 +675,11 @@ if __FILE__ == $0
544
675
  exit! 0
545
676
  end
546
677
 
547
- daemonize(opts[:debug]) do
678
+ daemonize(opts[:debug] || opts[:foreground]) do
548
679
  Net::IRC::Server.new(opts[:host], opts[:port], TwitterIrcGateway, opts).start
549
680
  end
550
681
  end
551
682
 
683
+ # Local Variables:
684
+ # coding: utf-8
685
+ # End: