net-irc 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -530,8 +530,8 @@ if __FILE__ == $0
530
530
  Dir.chdir "/"
531
531
  File.open("/dev/null") {|f|
532
532
  STDIN.reopen f
533
- STDOUT.reopen f
534
- STDERR.reopen f
533
+ STDOUT.reopen f, 'w'
534
+ STDERR.reopen f, 'w'
535
535
  }
536
536
  yield
537
537
  end
@@ -275,6 +275,7 @@ module Lingr
275
275
 
276
276
  def post(url, params)
277
277
  if !params.find {|p| p[1].is_a?(Hash)}
278
+ params = params.inject({}){|hash,(k,v)| hash[k.to_s] = v; hash}
278
279
  parse_result Net::HTTP.post_form(URI.parse(url), params)
279
280
  else
280
281
  boundary = 'lingr-api-client' + (0x1000000 + rand(0x1000000).to_s(16))
@@ -40,8 +40,10 @@ class Mixi
40
40
  @agent.submit form
41
41
 
42
42
  page = @agent.get "http://mixi.jp/home.pl"
43
- page = @agent.get page.links[18].uri
44
- form = page.forms[(@mixi_premium ? 1 : 0)]
43
+ #page = @agent.get page.links[18].uri
44
+ page = @agent.get page.links[14].uri
45
+ form = page.forms[1]
46
+ #form = page.forms[(@mixi_premium ? 1 : 0)]
45
47
  form.diary_title = title
46
48
  form.diary_body = self.class.magic_body(body)
47
49
  get_image images
@@ -54,13 +56,13 @@ class Mixi
54
56
  form.file_uploads[i].file_name = img
55
57
  end
56
58
  page = @agent.submit form
57
- page = @agent.submit page.forms[0]
59
+ page = @agent.submit page.forms[1]
58
60
  end
59
61
 
60
62
  def get_latest
61
63
  page = @agent.get 'http://mixi.jp/list_diary.pl'
62
- ["http://mixi.jp/" << page.links[37].uri.to_s.toutf8,
63
- page.links[37].text.toutf8]
64
+ ["http://mixi.jp/" << page.links[33].uri.to_s.toutf8,
65
+ page.links[33].text.toutf8]
64
66
  end
65
67
 
66
68
  def self.magic_body(body)
@@ -128,17 +130,30 @@ class MixiDiary < Net::IRC::Server::Session
128
130
  def on_privmsg(m)
129
131
  super
130
132
 
133
+ # CTCP にしたほうがよくないか?
131
134
  case m[1]
132
135
  when "."
133
- title, body = *@cont
134
- @mixi.post ">_<× < #{title}".toeuc, body.toeuc, []
136
+ title, *body = *@cont
137
+ @mixi.post ">_<× < #{title}".toeuc, body.join("\n").toeuc, []
135
138
  @mixi.get_latest.each do |line|
136
139
  post server_name, NOTICE, main_channel, line.chomp
137
140
  end
138
- when " "
141
+ when "c"
139
142
  @cont.clear
143
+ post server_name, NOTICE, main_channel, "cleared."
144
+ when "p"
145
+ @cont.each do |l|
146
+ post server_name, NOTICE, main_channel, l
147
+ end
148
+ post server_name, NOTICE, main_channel, "^^end"
149
+ when "d"
150
+ post server_name, NOTICE, main_channel, "Deleted last line: #{@cont.pop}"
140
151
  else
141
152
  @cont << m[1]
153
+ if @cont.size == 1
154
+ post server_name, NOTICE, main_channel, "start with title: #{@cont.first}"
155
+ else
156
+ end
142
157
  end
143
158
  end
144
159
 
@@ -63,7 +63,7 @@ class NowaIrcGateway < TwitterIrcGateway
63
63
  end
64
64
 
65
65
  def api_base
66
- URI("https://api.nowa.jp/")
66
+ URI("https://api.nowa.jp:443/")
67
67
  end
68
68
 
69
69
  def api_source
@@ -62,7 +62,7 @@ class ServerLogIrcGateway < Net::IRC::Server::Session
62
62
  :topic => "",
63
63
  :observer => nil,
64
64
  } unless @channels.key?(channel)
65
- post @prefix, JOIN, m.params.first
65
+ post @prefix, JOIN, channel
66
66
  post nil, RPL_NAMREPLY, @prefix.nick, "=", channel, "@#{@prefix.nick}"
67
67
  post nil, RPL_ENDOFNAMES, @prefix.nick, channel, "End of NAMES list"
68
68
  end
@@ -99,9 +99,10 @@ class ServerLogIrcGateway < Net::IRC::Server::Session
99
99
  nsize = File.size(f)
100
100
  if nsize > size
101
101
  @log.debug "follow up log"
102
- l = f.gets
103
- if grep === l
104
- post name, PRIVMSG, chan, l
102
+ while l = f.gets
103
+ if grep === l
104
+ post name, PRIVMSG, chan, l
105
+ end
105
106
  end
106
107
  end
107
108
  size = nsize
@@ -16,14 +16,14 @@ If you want to help:
16
16
 
17
17
  ## Configuration
18
18
 
19
- Options specified by after irc realname.
19
+ Options specified by after IRC realname.
20
20
 
21
21
  Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
22
22
 
23
23
  twitter {
24
24
  host: localhost
25
25
  port: 16668
26
- name: username@example.com athack jabber=username@example.com:jabberpasswd tid=10 ratio=32:1 replies=6
26
+ name: username@example.com athack jabber=username@example.com:jabberpasswd tid ratio=32:1 replies=6 maxlimit=70
27
27
  password: password on Twitter
28
28
  in-encoding: utf8
29
29
  out-encoding: utf8
@@ -40,11 +40,11 @@ it's good for Twitter like reply command (@nick).
40
40
  In this case, you will see torrent of join messages after connected,
41
41
  because NAMES list can't send @ leading nick (it interpreted op.)
42
42
 
43
- ### tid=<color>
43
+ ### tid[=<color>]
44
44
 
45
- Apply id to each message for make favorites by CTCP ACTION.
45
+ Apply ID to each message for make favorites by CTCP ACTION.
46
46
 
47
- /me fav id
47
+ /me fav ID [ID...]
48
48
 
49
49
  <color> can be
50
50
 
@@ -86,7 +86,44 @@ Use IM instead of any APIs (e.g. post)
86
86
 
87
87
  ### replies[=<ratio>]
88
88
 
89
- ### checkrls[=<interval seconds>]
89
+ ### maxlimit=<hourly limit>
90
+
91
+ ### checkrls=<interval seconds>
92
+
93
+ ### secure
94
+
95
+ Force SSL for API.
96
+
97
+ ## Extended commands through the CTCP ACTION
98
+
99
+ ### list (ls)
100
+
101
+ /me list NICK_or_screen_name
102
+
103
+ ### fav (favorite, favourite, unfav, unfavorite, unfavourite)
104
+
105
+ /me fav ID [ID...]
106
+ /me unfav ID [ID...]
107
+
108
+ ### link (ln)
109
+
110
+ /me link ID [ID...]
111
+
112
+ ### destroy (del, delete, miss, oops, remove, rm)
113
+
114
+ /me destroy ID [ID...]
115
+
116
+ ### in (location)
117
+
118
+ /me in Tokyo, Japan
119
+
120
+ ### reply (re)
121
+
122
+ /me reply ID blah, blah...
123
+
124
+ ### utf7
125
+
126
+ /me utf7
90
127
 
91
128
  ## License
92
129
 
@@ -101,7 +138,6 @@ $KCODE = "u" # json use this
101
138
 
102
139
  require "rubygems"
103
140
  require "net/irc"
104
- require "net/http"
105
141
  require "net/https"
106
142
  require "uri"
107
143
  require "json"
@@ -140,7 +176,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
140
176
  end
141
177
 
142
178
  def hourly_limit
143
- 20
179
+ 60
144
180
  end
145
181
 
146
182
  class ApiFailed < StandardError; end
@@ -149,7 +185,8 @@ class TwitterIrcGateway < Net::IRC::Server::Session
149
185
  super
150
186
  @groups = {}
151
187
  @channels = [] # joined channels (groups)
152
- @user_agent = "#{self.class}/#{server_version} (tig.rb)"
188
+ @nicknames = {}
189
+ @user_agent = "#{self.class}/#{server_version} (#{File.basename(__FILE__)})"
153
190
  @config = Pathname.new(ENV["HOME"]) + ".tig"
154
191
  @map = nil
155
192
  load_config
@@ -176,7 +213,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
176
213
  start_jabber(jid, pass)
177
214
  rescue LoadError
178
215
  log "Failed to start Jabber."
179
- log 'Installl "xmpp4r-simple" gem or check your id/pass.'
216
+ log 'Installl "xmpp4r-simple" gem or check your ID/pass.'
180
217
  finish
181
218
  end
182
219
  else
@@ -189,9 +226,11 @@ class TwitterIrcGateway < Net::IRC::Server::Session
189
226
  @log.info "Client Options: #{@opts.inspect}"
190
227
 
191
228
  @hourly_limit = hourly_limit
229
+
192
230
  @check_rate_limit_thread = Thread.start do
193
231
  loop do
194
232
  begin
233
+ check_downtime
195
234
  check_rate_limit
196
235
  rescue ApiFailed => e
197
236
  @log.error e.inspect
@@ -201,18 +240,17 @@ class TwitterIrcGateway < Net::IRC::Server::Session
201
240
  @log.error "\t#{l}"
202
241
  end
203
242
  end
204
- sleep @opts["checkrls"] || 3600 # 1 time / hour
243
+ sleep @opts["checkrls"] || 3600 # 1 hour
205
244
  end
206
245
  end
207
246
  sleep 5
208
247
 
209
- timeline_ratio, friends_ratio = (@opts["ratio"] || "10:3").split(":").map {|ratio| ratio.to_i }
210
- footing = (timeline_ratio + friends_ratio).to_f
248
+ @ratio = Struct.new(:timeline, :friends, :replies).new(*(@opts["ratio"] || "10:3").split(":").map {|ratio| ratio.to_f })
249
+ @ratio[:replies] = @opts.key?("replies") ? (@opts["replies"] || 5).to_f : 0.0
211
250
 
212
- if @opts.key?("replies")
213
- replies_ratio ||= (@opts["replies"] || 5).to_i
214
- footing += replies_ratio
215
- end
251
+ footing = @ratio.inject {|sum, ratio| sum + ratio }
252
+
253
+ @ratio.each_pair {|m, v| @ratio[m] = v / footing }
216
254
 
217
255
  @timeline = []
218
256
  @check_friends_thread = Thread.start do
@@ -227,7 +265,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
227
265
  @log.error "\t#{l}"
228
266
  end
229
267
  end
230
- sleep freq(friends_ratio / footing)
268
+ sleep freq(@ratio[:friends])
231
269
  end
232
270
  end
233
271
 
@@ -247,7 +285,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
247
285
  @log.error "\t#{l}"
248
286
  end
249
287
  end
250
- sleep freq(timeline_ratio / footing)
288
+ sleep freq(@ratio[:timeline])
251
289
  end
252
290
  end
253
291
 
@@ -266,7 +304,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
266
304
  @log.error "\t#{l}"
267
305
  end
268
306
  end
269
- sleep freq(replies_ratio / footing)
307
+ sleep freq(@ratio[:replies])
270
308
  end
271
309
  end
272
310
  end
@@ -285,17 +323,18 @@ class TwitterIrcGateway < Net::IRC::Server::Session
285
323
  retry_count = 3
286
324
  ret = nil
287
325
  target, message = *m.params
326
+ message = Iconv.iconv("UTF-7", "UTF-8", message).join if @utf7
288
327
  begin
289
328
  if target =~ /^#/
290
329
  if @opts.key?("alwaysim") && @im && @im.connected? # in jabber mode, using jabber post
291
330
  ret = @im.deliver(jabber_bot_id, message)
292
331
  post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(message)
293
332
  else
294
- ret = api("statuses/update", {"status" => message})
333
+ ret = api("statuses/update", { :status => message })
295
334
  end
296
335
  else
297
336
  # direct message
298
- ret = api("direct_messages/new", {"user" => target, "text" => message})
337
+ ret = api("direct_messages/new", { :user => target, :text => message })
299
338
  end
300
339
  raise ApiFailed, "API failed" unless ret
301
340
  log "Status Updated"
@@ -314,35 +353,131 @@ class TwitterIrcGateway < Net::IRC::Server::Session
314
353
  def on_ctcp(target, message)
315
354
  _, command, *args = message.split(/\s+/)
316
355
  case command
317
- when "list"
318
- nick = args[0]
319
- @log.debug([ nick, message ])
320
- res = api("statuses/user_timeline", { "id" => nick }).reverse_each do |s|
356
+ when "call" # /me call <twitter-id> as <nickname>
357
+ twitter_id = args[0]
358
+ nickname = args[2] || args[1] # allow omitting 'as'
359
+ if nickname == "is"
360
+ @nicknames.delete(twitter_id)
361
+ post server_name, NOTICE, main_channel, "Removed nickname for #{twitter_id}"
362
+ else
363
+ @nicknames[twitter_id] = nickname
364
+ post server_name, NOTICE, main_channel, "Call #{twitter_id} as #{nickname}"
365
+ end
366
+ when "utf7"
367
+ begin
368
+ require "iconv"
369
+ @utf7 = !@utf7
370
+ log "UTF-7 mode: #{@utf7 ? 'on' : 'off'}"
371
+ rescue LoadError => e
372
+ log "Can't load iconv."
373
+ end
374
+ when "list", "ls"
375
+ nick = args.first
376
+ unless (1..200).include?(count = args[1].to_i)
377
+ count = 20
378
+ end
379
+ @log.debug([nick, message])
380
+ to = nick == @nick ? server_name : nick
381
+ res = api("statuses/user_timeline", { :id => nick, :count => "#{count}" }).reverse_each do |s|
321
382
  @log.debug(s)
322
- post nick, NOTICE, main_channel, "#{generate_status_message(s)}"
383
+ post to, NOTICE, main_channel, "#{generate_status_message(s)}"
323
384
  end
324
-
325
385
  unless res
326
- post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
386
+ post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
327
387
  end
328
- when "fav"
329
- tid = args[0]
330
- st = @tmap[tid]
331
- if st
332
- id = st["id"] || st["rid"]
333
- res = api("favorites/create/#{id}", {})
334
- post nil, NOTICE, main_channel, "Fav: #{res["screen_name"]}: #{res["text"]}"
335
- else
336
- post nil, NOTICE, main_channel, "No such id #{tid}"
388
+ when /^(un)?fav(?:ou?rite)?$/
389
+ method, pfx = $1.nil? ? ["create", "F"] : ["destroy", "Unf"]
390
+ args.each_with_index do |tid, i|
391
+ st = @tmap[tid]
392
+ if st
393
+ sleep 1 if i > 0
394
+ res = api("favorites/#{method}/#{st["id"]}")
395
+ post server_name, NOTICE, main_channel, "#{pfx}av: #{res["user"]["screen_name"]}: #{res["text"]}"
396
+ else
397
+ post server_name, NOTICE, main_channel, "No such ID #{tid}"
398
+ end
399
+ end
400
+ when "link", "ln"
401
+ args.each do |tid|
402
+ st = @tmap[tid]
403
+ if st
404
+ post server_name, NOTICE, main_channel, "#{api_base + st["user"]["screen_name"]}/statuses/#{st["id"]}"
405
+ else
406
+ post server_name, NOTICE, main_channel, "No such ID #{tid}"
407
+ end
408
+ end
409
+ # when /^ratios?$/
410
+ # if args[1].nil? ||
411
+ # @opts.key?("replies") && args[2].nil?
412
+ # return post server_name, NOTICE, main_channel, "/me ratios <timeline> <frends>[ <replies>]"
413
+ # end
414
+ # ratios = args.map {|ratio| ratio.to_f }
415
+ # if ratios.any? {|ratio| ratio <= 0.0 }
416
+ # return post server_name, NOTICE, main_channel, "Ratios must be greater than 0."
417
+ # end
418
+ # footing = ratios.inject {|sum, ratio| sum + ratio }
419
+ # @ratio[:timeline] = ratios[0]
420
+ # @ratio[:friends] = ratios[1]
421
+ # @ratio[:replies] = ratios[2] || 0.0
422
+ # @ratio.each_pair {|m, v| @ratio[m] = v / footing }
423
+ # intervals = @ratio.map {|ratio| freq ratio }
424
+ # post server_name, NOTICE, main_channel, "Intervals: #{intervals.join(", ")}"
425
+ when /^(?:de(?:stroy|l(?:ete)?)|miss|oops|r(?:emove|m))$/ # destroy, delete, del, remove, rm, miss, oops
426
+ args.each_with_index do |tid, i|
427
+ st = @tmap[tid]
428
+ if st
429
+ sleep 1 if i > 0
430
+ res = api("statuses/destroy/#{st["id"]}")
431
+ post server_name, NOTICE, main_channel, "Destroyed: #{res["text"]}"
432
+ else
433
+ post server_name, NOTICE, main_channel, "No such ID #{tid}"
434
+ end
435
+ end
436
+ when "name"
437
+ name = message.split(/\s+/, 3)[2]
438
+ unless name.nil?
439
+ api("account/update_profile", { :name => name })
440
+ post server_name, NOTICE, main_channel, "You are named #{name}."
337
441
  end
338
- when "link"
339
- tid = args[0]
442
+ when "email"
443
+ # FIXME
444
+ email = args.first
445
+ unless email.nil?
446
+ api("account/update_profile", { :email => email })
447
+ end
448
+ when "url"
449
+ # FIXME
450
+ url = args.first || ""
451
+ api("account/update_profile", { :url => url })
452
+ when "in", "location"
453
+ location = message.split(/\s+/, 3)[2] || ""
454
+ api("account/update_profile", { :location => location })
455
+ location = location.empty? ? "nowhere" : "in #{location}"
456
+ post server_name, NOTICE, main_channel, "You are #{location} now."
457
+ when /^desc(?:ription)?$/
458
+ # FIXME
459
+ description = message.split(/\s+/, 3)[2] || ""
460
+ api("account/update_profile", { :description => description })
461
+ when /^colou?rs?$/
462
+ # FIXME
463
+ # bg, text, link, fill and border
464
+ when "image", "img"
465
+ # FIXME
466
+ url = args.first
467
+ # TODO: DCC SEND
468
+ when "follow"
469
+ # FIXME
470
+ when "leave"
471
+ # FIXME
472
+ when /^re(?:ply)?$/
473
+ tid = args.first
340
474
  st = @tmap[tid]
341
475
  if st
342
- st["link"] = (api_base + "/#{st["user"]["screen_name"]}/statuses/#{st["id"]}").to_s unless st["link"]
343
- post nil, NOTICE, main_channel, st["link"]
344
- else
345
- post nil, NOTICE, main_channel, "No such id #{tid}"
476
+ msg = message.split(/\s+/, 4)[3]
477
+ ret = api("statuses/update", { :status => msg, :in_reply_to_status_id => "#{st["id"]}" })
478
+ if ret
479
+ log "Status updated (In reply to \x03#{@opts["tid"] || 10}[#{tid}]\x0f <#{api_base + st["user"]["screen_name"]}/statuses/#{st["id"]}>)"
480
+ end
346
481
  end
347
482
  end
348
483
  rescue ApiFailed => e
@@ -353,12 +488,12 @@ class TwitterIrcGateway < Net::IRC::Server::Session
353
488
  nick = m.params[0]
354
489
  f = (@friends || []).find {|i| i["screen_name"] == nick }
355
490
  if f
356
- post nil, RPL_WHOISUSER, @nick, nick, nick, api_base.host, "*", "#{f["name"]} / #{f["description"]}"
357
- post nil, RPL_WHOISSERVER, @nick, nick, api_base.host, api_base.to_s
358
- post nil, RPL_WHOISIDLE, @nick, nick, "0", "seconds idle"
359
- post nil, RPL_ENDOFWHOIS, @nick, nick, "End of WHOIS list"
491
+ post server_name, RPL_WHOISUSER, @nick, nick, nick, api_base.host, "*", "#{f["name"]} / #{f["description"]}"
492
+ post server_name, RPL_WHOISSERVER, @nick, nick, api_base.host, api_base.to_s
493
+ post server_name, RPL_WHOISIDLE, @nick, nick, "0", "seconds idle"
494
+ post server_name, RPL_ENDOFWHOIS, @nick, nick, "End of WHOIS list"
360
495
  else
361
- post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
496
+ post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
362
497
  end
363
498
  end
364
499
 
@@ -373,20 +508,20 @@ class TwitterIrcGateway < Net::IRC::Server::Session
373
508
  user = nick = f["screen_name"]
374
509
  host = serv = api_base.host
375
510
  real = f["name"]
376
- post nil, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
511
+ post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
377
512
  end
378
- post nil, RPL_ENDOFWHO, @nick, channel
513
+ post server_name, RPL_ENDOFWHO, @nick, channel
379
514
  when @groups.key?(channel)
380
515
  @groups[channel].each do |name|
381
516
  f = @friends.find {|i| i["screen_name"] == name }
382
517
  user = nick = f["screen_name"]
383
518
  host = serv = api_base.host
384
519
  real = f["name"]
385
- post nil, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
520
+ post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
386
521
  end
387
- post nil, RPL_ENDOFWHO, @nick, channel
522
+ post server_name, RPL_ENDOFWHO, @nick, channel
388
523
  else
389
- post nil, ERR_NOSUCHNICK, @nick, nick, "No such nick/channel"
524
+ post server_name, ERR_NOSUCHNICK, @nick, nick, "No such nick/channel"
390
525
  end
391
526
  end
392
527
 
@@ -440,35 +575,34 @@ class TwitterIrcGateway < Net::IRC::Server::Session
440
575
 
441
576
  private
442
577
  def check_timeline
443
- @prev_time ||= Time.at(0)
444
- api("statuses/friends_timeline", {"since" => @prev_time.httpdate}).reverse_each do |s|
445
- id = s["id"] || s["rid"]
578
+ q = { :count => "117" }
579
+ q[:since_id] = @timeline.last.to_s if @timeline.last
580
+ api("statuses/friends_timeline", q).reverse_each do |s|
581
+ id = s["id"]
446
582
  next if id.nil? || @timeline.include?(id)
583
+
447
584
  @timeline << id
448
585
  nick = s["user"]["screen_name"]
449
586
  mesg = generate_status_message(s)
587
+ tid = @tmap.push(s)
450
588
 
451
- tid = @tmap.push(s)
589
+ if @opts.key?("tid")
590
+ mesg = "%s \x03%s[%s]" % [mesg, @opts["tid"] || 10, tid]
591
+ end
452
592
 
453
593
  @log.debug [id, nick, mesg]
454
- if nick == @nick # 自分のときは topic
594
+ if nick == @nick # 自分のときは TOPIC
455
595
  post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(mesg)
456
596
  else
457
- if @opts["tid"]
458
- message(nick, main_channel, "%s \x03%s [%s]" % [mesg, @opts["tid"], tid])
459
- else
460
- message(nick, main_channel, "%s" % [mesg, tid])
461
- end
597
+ message(nick, main_channel, mesg)
462
598
  end
463
599
  @groups.each do |channel, members|
464
- if members.include?(nick)
465
- message(nick, channel, "%s [%s]" % [mesg, tid])
466
- end
600
+ next unless members.include?(nick)
601
+ message(nick, channel, mesg)
467
602
  end
468
603
  end
469
604
  @log.debug "@timeline.size = #{@timeline.size}"
470
- @timeline = @timeline.last(100)
471
- @prev_time = Time.now
605
+ @timeline = @timeline.last(200)
472
606
  end
473
607
 
474
608
  def generate_status_message(status)
@@ -476,47 +610,53 @@ class TwitterIrcGateway < Net::IRC::Server::Session
476
610
  mesg = s["text"]
477
611
  @log.debug(mesg)
478
612
 
613
+ begin
614
+ require "iconv"
615
+ mesg = mesg.sub(/^.+ > |^.+/) {|str| Iconv.iconv("UTF-8", "UTF-7", str).join }
616
+ mesg = "[utf7]: #{mesg}" if body =~ /[^a-z0-9\s]/i
617
+ rescue LoadError
618
+ rescue Iconv::IllegalSequence
619
+ end
620
+
479
621
  # time = Time.parse(s["created_at"]) rescue Time.now
480
- m = { "&quot;" => "\"", "&lt;"=> "<", "&gt;"=> ">", "&amp;"=> "&", "\n" => " "}
481
- mesg.gsub!(/(#{m.keys.join("|")})/) { m[$1] }
622
+ m = { "&quot;" => "\"", "&lt;" => "<", "&gt;" => ">", "&amp;" => "&", "\n" => " " }
623
+ mesg.gsub!(/#{m.keys.join("|")}/) { m[$&] }
482
624
  mesg
483
625
  end
484
626
 
485
627
  def check_replies
486
- @prev_time_r ||= Time.now
628
+ time = @prev_time_r || Time.now
629
+ @prev_time_r = Time.now
487
630
  api("statuses/replies").reverse_each do |s|
488
- id = s["id"] || s["rid"]
631
+ id = s["id"]
489
632
  next if id.nil? || @timeline.include?(id)
490
- time = Time.parse(s["created_at"]) rescue next
491
- next if time < @prev_time_r
492
- @timeline << id
493
- nick = s["user_login_id"] || s["user"]["screen_name"]
633
+
634
+ created_at = Time.parse(s["created_at"]) rescue next
635
+ next if created_at < time
636
+
637
+ nick = s["user"]["screen_name"]
494
638
  mesg = generate_status_message(s)
639
+ tid = @tmap.push(s)
495
640
 
496
- tid = @tmap.push(s)
641
+ if @opts.key?("tid")
642
+ mesg = "%s \x03%s[%s]" % [mesg, @opts["tid"] || 10, tid]
643
+ end
497
644
 
498
645
  @log.debug [id, nick, mesg]
499
- if @opts["tid"]
500
- message(nick, main_channel, "%s \x03%s [%s]" % [mesg, @opts["tid"], tid])
501
- else
502
- message(nick, main_channel, "%s" % mesg)
503
- end
646
+ message nick, main_channel, mesg
504
647
  end
505
- @log.debug "@timeline.size = #{@timeline.size}"
506
- @timeline = @timeline.last(100)
507
- @prev_time_r = Time.now
508
648
  end
509
649
 
510
650
  def check_direct_messages
511
- @prev_time_d ||= Time.now
512
- api("direct_messages", {"since" => @prev_time_d.httpdate}).reverse_each do |s|
651
+ time = @prev_time_d || Time.now
652
+ @prev_time_d = Time.now
653
+ api("direct_messages", { :since => time.httpdate }).reverse_each do |s|
513
654
  nick = s["sender_screen_name"]
514
655
  mesg = s["text"]
515
656
  time = Time.parse(s["created_at"])
516
657
  @log.debug [nick, mesg, time].inspect
517
658
  message(nick, @nick, mesg)
518
659
  end
519
- @prev_time_d = Time.now
520
660
  end
521
661
 
522
662
  def check_friends
@@ -525,8 +665,8 @@ class TwitterIrcGateway < Net::IRC::Server::Session
525
665
  friends = api("statuses/friends")
526
666
  if first && !@opts.key?("athack")
527
667
  @friends = friends
528
- post nil, RPL_NAMREPLY, @nick, "=", main_channel, @friends.map{|i| "@#{i["screen_name"]}" }.join(" ")
529
- post nil, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
668
+ post server_name, RPL_NAMREPLY, @nick, "=", main_channel, @friends.map{|i| "@#{i["screen_name"]}" }.join(" ")
669
+ post server_name, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
530
670
  else
531
671
  prv_friends = @friends.map {|i| i["screen_name"] }
532
672
  now_friends = friends.map {|i| i["screen_name"] }
@@ -548,19 +688,37 @@ class TwitterIrcGateway < Net::IRC::Server::Session
548
688
 
549
689
  def check_rate_limit
550
690
  @log.debug rate_limit = api("account/rate_limit_status")
551
- if @hourly_limit != rate_limit["hourly_limit"]
552
- log "Rate limit changed: #{@hourly_limit} to #{rate_limit["hourly_limit"]}"
553
- @log.info "Rate limit changed: #{@hourly_limit} to #{rate_limit["hourly_limit"]}"
691
+ if rate_limit.key?("hourly_limit") && @hourly_limit != rate_limit["hourly_limit"]
692
+ msg = "Rate limit was changed: #{@hourly_limit} to #{rate_limit["hourly_limit"]}"
693
+ log msg
694
+ @log.info msg
554
695
  @hourly_limit = rate_limit["hourly_limit"]
555
696
  end
556
697
  # rate_limit["remaining_hits"] < 1
557
698
  # rate_limit["reset_time_in_seconds"] - Time.now.to_i
558
699
  end
559
700
 
701
+ def check_downtime
702
+ @prev_downtime ||= nil
703
+ schedule = api("help/downtime_schedule", {}, { :suppress_errors => true })["error"]
704
+ if @prev_downtime != schedule && @prev_downtime = schedule
705
+ msg = schedule.gsub(%r{[\r\n]|<style(?:\s[^>]*)?>.*?</style\s*>}m, "")
706
+ uris = URI.extract(msg)
707
+ uris.each do |uri|
708
+ msg << " #{uri}"
709
+ end
710
+ msg.gsub!(/<[^>]+>/, "")
711
+ log "\002\037#{msg}\017"
712
+ # TODO: sleeping for the downtime
713
+ end
714
+ end
715
+
560
716
  def freq(ratio)
561
- ret = 3600 / (@hourly_limit * ratio).round
562
- @log.debug "Frequency: #{ret}"
563
- ret
717
+ max = (@opts["maxlimit"] || 100).to_i
718
+ limit = @hourly_limit < max ? @hourly_limit : max
719
+ f = 3600 / (limit * ratio).round
720
+ @log.debug "Frequency: #{f}"
721
+ f
564
722
  end
565
723
 
566
724
  def start_jabber(jid, pass)
@@ -575,6 +733,15 @@ class TwitterIrcGateway < Net::IRC::Server::Session
575
733
  if msg.from.strip == jabber_bot_id
576
734
  # Twitter -> 'id: msg'
577
735
  body = msg.body.sub(/^(.+?)(?:\((.+?)\))?: /, "")
736
+
737
+ begin
738
+ require "iconv"
739
+ body = body.sub(/^.+ > |^.+/) {|str| Iconv.iconv("UTF-8", "UTF-7", str).join }
740
+ body = "[utf7]: #{body}" if body =~ /[^a-z0-9\s]/i
741
+ rescue LoadError
742
+ rescue Iconv::IllegalSequence
743
+ end
744
+
578
745
  if Regexp.last_match
579
746
  nick, id = Regexp.last_match.captures
580
747
  body = CGI.unescapeHTML(body)
@@ -612,40 +779,72 @@ class TwitterIrcGateway < Net::IRC::Server::Session
612
779
  rescue Errno::ENOENT
613
780
  end
614
781
 
615
- def api(path, q={})
782
+ def require_post?(path)
783
+ %r{
784
+ ^
785
+ (?: status(?:es)?/update $
786
+ | direct_messages/new $
787
+ | friendships/create/
788
+ | account/ (?: end_session $
789
+ | update_ )
790
+ | favou?ri(?:ing|tes)/create/
791
+ | notifications/
792
+ | blocks/create/ )
793
+ }x === path
794
+ end
795
+
796
+ def require_delete?(path)
797
+ #%r{
798
+ # ^
799
+ # (?: status(?:es)?
800
+ # | direct_messages
801
+ # | friendships
802
+ # | favou?ri(?:ing|tes) )
803
+ # | blocks
804
+ # /destroy/
805
+ #}x === path
806
+ path.include? "/destroy/"
807
+ end
808
+
809
+ def api(path, q = {}, opt = {})
616
810
  ret = {}
617
- headers = {
618
- "User-Agent" => @user_agent,
619
- "Authorization" => "Basic " + ["#{@real}:#{@pass}"].pack("m"),
620
- }
811
+ headers = { "User-Agent" => @user_agent }
621
812
  headers["If-Modified-Since"] = q["since"] if q.key?("since")
622
813
 
623
814
  q["source"] ||= api_source
624
- q = q.inject([]) {|r,(k,v)| v.inject(r) {|r,i| r << "#{k}=#{URI.escape(i, /[^-.!~*'()\w]/n)}" } }.join("&")
625
-
626
- uri = api_base.dup
627
- uri.path = path.sub(%r{^/*}, "/") << ".json"
628
- uri.query = q
629
815
 
816
+ path = path.sub(%r{^/+}, "")
817
+ uri = api_base.dup
818
+ if @opts.key?("secure")
819
+ uri.scheme = "https"
820
+ uri.port = 443
821
+ end
822
+ uri.path += "#{path}.json"
823
+ uri.query = q.inject([]) {|r,(k,v)| v ? r << "#{k}=#{URI.escape(v, /[^-.!~*'()\w]/n)}" : r }.join("&")
824
+ case
825
+ when require_post?(path)
826
+ req = Net::HTTP::Post.new(uri.path, headers)
827
+ req.body = uri.query
828
+ when require_delete?(path)
829
+ req = Net::HTTP::Delete.new(uri.path, headers)
830
+ req.body = uri.query
831
+ else
832
+ req = Net::HTTP::Get.new(uri.request_uri, headers)
833
+ end
834
+ req.basic_auth(@real, @pass)
630
835
  @log.debug uri.inspect
836
+
631
837
  http = Net::HTTP.new(uri.host, uri.port)
632
838
  if uri.scheme == "https"
633
839
  http.use_ssl = true
634
840
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE # FIXME
635
841
  end
636
- http.start do
637
- case uri.path
638
- when "/statuses/update.json", "/direct_messages/new.json"
639
- ret = http.post(uri.request_uri, q, headers)
640
- else
641
- ret = http.get(uri.request_uri, headers)
642
- end
643
- end
644
-
645
- case ret
842
+ case ret = http.request(req)
646
843
  when Net::HTTPOK # 200
647
844
  ret = JSON.parse(ret.body.gsub(/'(y(?:es)?|no?|true|false|null)'/, '"\1"'))
648
- raise ApiFailed, "Server Returned Error: #{ret["error"]}" if ret.kind_of?(Hash) && ret["error"]
845
+ if ret.kind_of?(Hash) && !opt[:suppress_errors] && ret["error"]
846
+ raise ApiFailed, "Server Returned Error: #{ret["error"]}"
847
+ end
649
848
  ret
650
849
  when Net::HTTPNotModified # 304
651
850
  []
@@ -664,17 +863,18 @@ class TwitterIrcGateway < Net::IRC::Server::Session
664
863
  # [$1 ? $2.hex : $2.to_i].pack("U")
665
864
  # end
666
865
  str = untinyurl(str)
866
+ sender = @nicknames[sender] || sender
667
867
  sender = "#{sender}!#{sender}@#{api_base.host}"
668
868
  post sender, PRIVMSG, target, str
669
869
  end
670
870
 
671
871
  def log(str)
672
- str.gsub!(/\n/, " ")
872
+ str.gsub!(/\r?\n|\r/, " ")
673
873
  post server_name, NOTICE, main_channel, str
674
874
  end
675
875
 
676
876
  def untinyurl(text)
677
- text.gsub(%r|http://(preview\.)?tinyurl\.com/[0-9a-z=]+|i) {|m|
877
+ text.gsub(%r"http://(?:(preview\.)?tin|rub)yurl\.com/[0-9a-z=]+"i) {|m|
678
878
  uri = URI(m)
679
879
  uri.host = uri.host.sub($1, "") if $1
680
880
  Net::HTTP.start(uri.host, uri.port) {|http|
@@ -689,7 +889,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
689
889
  end
690
890
 
691
891
  class TypableMap < Hash
692
- Roma = %w|k g ky gy s z sh j t d ch n ny h b p hy by py m my y r ry w v q|.unshift("").map {|consonant|
892
+ Roman = %w|k g ky gy s z sh j t d ch n ny h b p hy by py m my y r ry w v q|.unshift("").map {|consonant|
693
893
  case
694
894
  when consonant.size > 1, consonant == "y"
695
895
  %w|a u o|
@@ -700,8 +900,8 @@ class TwitterIrcGateway < Net::IRC::Server::Session
700
900
  end.map {|vowel| "#{consonant}#{vowel}" }
701
901
  }.flatten
702
902
 
703
- def initialize(size=1)
704
- @seq = Roma
903
+ def initialize(size = 1)
904
+ @seq = Roman
705
905
  @map = {}
706
906
  @n = 0
707
907
  @size = size
@@ -791,7 +991,7 @@ if __FILE__ == $0
791
991
  opts[:logger] = Logger.new(opts[:log], "daily")
792
992
  opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
793
993
 
794
- # def daemonize(foreground=false)
994
+ # def daemonize(foreground = false)
795
995
  # trap("SIGINT") { exit! 0 }
796
996
  # trap("SIGTERM") { exit! 0 }
797
997
  # trap("SIGHUP") { exit! 0 }