net-irc 0.0.2 → 0.0.3

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