net-irc 0.0.4 → 0.0.5
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/ChangeLog +16 -0
- data/Rakefile +1 -1
- data/examples/nig.rb +4 -0
- data/examples/tig.rb +159 -72
- data/examples/wig.rb +638 -32
- data/lib/net/irc.rb +1 -1
- data/lib/net/irc/client.rb +13 -129
- data/lib/net/irc/client/channel_manager.rb +144 -0
- data/lib/net/irc/constants.rb +1 -1
- data/lib/net/irc/message.rb +3 -1
- data/lib/net/irc/message/modeparser.rb +66 -25
- data/lib/net/irc/message/serverconfig.rb +30 -0
- data/spec/channel_manager_spec.rb +176 -0
- data/spec/modeparser_spec.rb +141 -19
- data/spec/net-irc_spec.rb +9 -69
- metadata +8 -6
- data/lib/net/irc/message/modeparser/hyperion.rb +0 -88
- data/lib/net/irc/message/modeparser/rfc1459.rb +0 -21
data/ChangeLog
CHANGED
@@ -1,9 +1,25 @@
|
|
1
|
+
2008-07-06 SATOH Hiroh <cho45@lowreal.net>
|
2
|
+
|
3
|
+
* [interface]:
|
4
|
+
Removed around @channels and separeted to
|
5
|
+
Net::IRC::Client::ChannelManager as just a sample of managing
|
6
|
+
channels.
|
7
|
+
* [release]:
|
8
|
+
Released 0.0.5
|
9
|
+
|
10
|
+
2008-07-06 Satoshi Nakagawa <psychs@limechat.net>
|
11
|
+
|
12
|
+
* [new]:
|
13
|
+
Added a mode parser which can be configured automatically from 005 replies.
|
14
|
+
|
1
15
|
2008-06-28 cho45
|
2
16
|
|
3
17
|
* [interface]:
|
4
18
|
Change mode character to symbol.
|
5
19
|
* [new]:
|
6
20
|
Seperate each class to some files.
|
21
|
+
* [release]:
|
22
|
+
Released 0.0.4
|
7
23
|
|
8
24
|
2008-06-14 cho45
|
9
25
|
|
data/Rakefile
CHANGED
@@ -18,7 +18,7 @@ require "net/irc"
|
|
18
18
|
NAME = "net-irc"
|
19
19
|
AUTHOR = "cho45"
|
20
20
|
EMAIL = "cho45@lowreal.net"
|
21
|
-
DESCRIPTION = ""
|
21
|
+
DESCRIPTION = "library for implementing IRC server and client"
|
22
22
|
RUBYFORGE_PROJECT = "lowreal"
|
23
23
|
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
24
24
|
BIN_FILES = %w( )
|
data/examples/nig.rb
CHANGED
data/examples/tig.rb
CHANGED
@@ -8,7 +8,7 @@ Ruby version of TwitterIrcGateway
|
|
8
8
|
|
9
9
|
## Launch
|
10
10
|
|
11
|
-
$ ruby tig.rb
|
11
|
+
$ ruby tig.rb
|
12
12
|
|
13
13
|
If you want to help:
|
14
14
|
|
@@ -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 jabber=username@example.com:jabberpasswd tid=10
|
26
|
+
name: username@example.com athack jabber=username@example.com:jabberpasswd tid=10 ratio=32:1 replies=6
|
27
27
|
password: password on Twitter
|
28
28
|
in-encoding: utf8
|
29
29
|
out-encoding: utf8
|
@@ -34,7 +34,7 @@ Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
|
|
34
34
|
If `athack` client option specified,
|
35
35
|
all nick in join message is leading with @.
|
36
36
|
|
37
|
-
So if you complemente nicks (
|
37
|
+
So if you complemente nicks (e.g. Irssi),
|
38
38
|
it's good for Twitter like reply command (@nick).
|
39
39
|
|
40
40
|
In this case, you will see torrent of join messages after connected,
|
@@ -61,7 +61,7 @@ Apply id to each message for make favorites by CTCP ACTION.
|
|
61
61
|
10 => teal
|
62
62
|
11 => lightcyan cyan aqua
|
63
63
|
12 => lightblue royal
|
64
|
-
13 => pink lightpurple
|
64
|
+
13 => pink lightpurple fuchsia
|
65
65
|
14 => grey
|
66
66
|
15 => lightgrey silver
|
67
67
|
|
@@ -72,7 +72,7 @@ If `jabber=<jid>:<pass>` option specified,
|
|
72
72
|
use jabber to get friends timeline.
|
73
73
|
|
74
74
|
You must setup im notifing settings in the site and
|
75
|
-
install
|
75
|
+
install "xmpp4r-simple" gem.
|
76
76
|
|
77
77
|
$ sudo gem install xmpp4r-simple
|
78
78
|
|
@@ -80,10 +80,15 @@ Be careful for managing password.
|
|
80
80
|
|
81
81
|
### alwaysim
|
82
82
|
|
83
|
-
Use IM instead of any APIs (
|
83
|
+
Use IM instead of any APIs (e.g. post)
|
84
84
|
|
85
|
+
### ratio=<timeline>:<friends>
|
85
86
|
|
86
|
-
|
87
|
+
### replies[=<ratio>]
|
88
|
+
|
89
|
+
### checkrls[=<interval seconds>]
|
90
|
+
|
91
|
+
## License
|
87
92
|
|
88
93
|
Ruby's by cho45
|
89
94
|
|
@@ -134,6 +139,10 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
134
139
|
"twitter@twitter.com"
|
135
140
|
end
|
136
141
|
|
142
|
+
def hourly_limit
|
143
|
+
20
|
144
|
+
end
|
145
|
+
|
137
146
|
class ApiFailed < StandardError; end
|
138
147
|
|
139
148
|
def initialize(*args)
|
@@ -153,13 +162,13 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
153
162
|
|
154
163
|
@real, *@opts = @opts.name || @real.split(/\s+/)
|
155
164
|
@opts = @opts.inject({}) {|r,i|
|
156
|
-
key, value = i.split(
|
165
|
+
key, value = i.split("=")
|
157
166
|
r.update(key => value)
|
158
167
|
}
|
159
|
-
@tmap
|
168
|
+
@tmap = TypableMap.new
|
160
169
|
|
161
170
|
if @opts["jabber"]
|
162
|
-
jid, pass = @opts["jabber"].split(
|
171
|
+
jid, pass = @opts["jabber"].split(":", 2)
|
163
172
|
@opts["jabber"].replace("jabber=#{jid}:********")
|
164
173
|
if jabber_bot_id
|
165
174
|
begin
|
@@ -167,7 +176,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
167
176
|
start_jabber(jid, pass)
|
168
177
|
rescue LoadError
|
169
178
|
log "Failed to start Jabber."
|
170
|
-
log
|
179
|
+
log 'Installl "xmpp4r-simple" gem or check your id/pass.'
|
171
180
|
finish
|
172
181
|
end
|
173
182
|
else
|
@@ -179,6 +188,32 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
179
188
|
log "Client Options: #{@opts.inspect}"
|
180
189
|
@log.info "Client Options: #{@opts.inspect}"
|
181
190
|
|
191
|
+
@hourly_limit = hourly_limit
|
192
|
+
@check_rate_limit_thread = Thread.start do
|
193
|
+
loop do
|
194
|
+
begin
|
195
|
+
check_rate_limit
|
196
|
+
rescue ApiFailed => e
|
197
|
+
@log.error e.inspect
|
198
|
+
rescue Exception => e
|
199
|
+
@log.error e.inspect
|
200
|
+
e.backtrace.each do |l|
|
201
|
+
@log.error "\t#{l}"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
sleep @opts["checkrls"] || 3600 # 1 time / hour
|
205
|
+
end
|
206
|
+
end
|
207
|
+
sleep 5
|
208
|
+
|
209
|
+
timeline_ratio, friends_ratio = (@opts["ratio"] || "10:3").split(":").map {|ratio| ratio.to_i }
|
210
|
+
footing = (timeline_ratio + friends_ratio).to_f
|
211
|
+
|
212
|
+
if @opts.key?("replies")
|
213
|
+
replies_ratio ||= (@opts["replies"] || 5).to_i
|
214
|
+
footing += replies_ratio
|
215
|
+
end
|
216
|
+
|
182
217
|
@timeline = []
|
183
218
|
@check_friends_thread = Thread.start do
|
184
219
|
loop do
|
@@ -192,13 +227,13 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
192
227
|
@log.error "\t#{l}"
|
193
228
|
end
|
194
229
|
end
|
195
|
-
sleep
|
230
|
+
sleep freq(friends_ratio / footing)
|
196
231
|
end
|
197
232
|
end
|
198
|
-
sleep 3
|
199
233
|
|
200
234
|
return if @opts["jabber"]
|
201
235
|
|
236
|
+
sleep 3
|
202
237
|
@check_timeline_thread = Thread.start do
|
203
238
|
loop do
|
204
239
|
begin
|
@@ -212,16 +247,37 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
212
247
|
@log.error "\t#{l}"
|
213
248
|
end
|
214
249
|
end
|
215
|
-
sleep
|
250
|
+
sleep freq(timeline_ratio / footing)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
return unless @opts.key?("replies")
|
255
|
+
|
256
|
+
sleep 10
|
257
|
+
@check_replies_thread = Thread.start do
|
258
|
+
loop do
|
259
|
+
begin
|
260
|
+
check_replies
|
261
|
+
rescue ApiFailed => e
|
262
|
+
@log.error e.inspect
|
263
|
+
rescue Exception => e
|
264
|
+
@log.error e.inspect
|
265
|
+
e.backtrace.each do |l|
|
266
|
+
@log.error "\t#{l}"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
sleep freq(replies_ratio / footing)
|
216
270
|
end
|
217
271
|
end
|
218
272
|
end
|
219
273
|
|
220
274
|
def on_disconnected
|
221
|
-
@check_friends_thread.kill
|
222
|
-
@
|
223
|
-
@
|
224
|
-
@
|
275
|
+
@check_friends_thread.kill rescue nil
|
276
|
+
@check_replies_thread.kill rescue nil
|
277
|
+
@check_timeline_thread.kill rescue nil
|
278
|
+
@check_rate_limit_thread.kill rescue nil
|
279
|
+
@im_thread.kill rescue nil
|
280
|
+
@im.disconnect rescue nil
|
225
281
|
end
|
226
282
|
|
227
283
|
def on_privmsg(m)
|
@@ -241,7 +297,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
241
297
|
# direct message
|
242
298
|
ret = api("direct_messages/new", {"user" => target, "text" => message})
|
243
299
|
end
|
244
|
-
raise ApiFailed, "
|
300
|
+
raise ApiFailed, "API failed" unless ret
|
245
301
|
log "Status Updated"
|
246
302
|
rescue => e
|
247
303
|
@log.error [retry_count, e.inspect].inspect
|
@@ -261,7 +317,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
261
317
|
when "list"
|
262
318
|
nick = args[0]
|
263
319
|
@log.debug([ nick, message ])
|
264
|
-
res = api(
|
320
|
+
res = api("statuses/user_timeline", { "id" => nick }).reverse_each do |s|
|
265
321
|
@log.debug(s)
|
266
322
|
post nick, NOTICE, main_channel, "#{generate_status_message(s)}"
|
267
323
|
end
|
@@ -389,7 +445,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
389
445
|
id = s["id"] || s["rid"]
|
390
446
|
next if id.nil? || @timeline.include?(id)
|
391
447
|
@timeline << id
|
392
|
-
nick = s["
|
448
|
+
nick = s["user"]["screen_name"]
|
393
449
|
mesg = generate_status_message(s)
|
394
450
|
|
395
451
|
tid = @tmap.push(s)
|
@@ -420,26 +476,37 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
420
476
|
mesg = s["text"]
|
421
477
|
@log.debug(mesg)
|
422
478
|
|
423
|
-
# added @user in no use @user reply message ( Wassr only )
|
424
|
-
if s.has_key?('reply_status_url') and s['reply_status_url'] and s['text'] !~ /^@.*/ and %r{([^/]+)/statuses/[^/]+}.match(s['reply_status_url'])
|
425
|
-
reply_user_id = $1
|
426
|
-
mesg = "@#{reply_user_id} #{mesg}"
|
427
|
-
end
|
428
|
-
# display area name(Wassr only)
|
429
|
-
if s.has_key?('areaname') and s["areaname"]
|
430
|
-
mesg += " L: #{s['areaname']}"
|
431
|
-
end
|
432
|
-
# display photo url(Wassr only)
|
433
|
-
if s.has_key?('photo_url') and s["photo_url"]
|
434
|
-
mesg += " #{s['photo_url']}"
|
435
|
-
end
|
436
|
-
|
437
479
|
# time = Time.parse(s["created_at"]) rescue Time.now
|
438
480
|
m = { """ => "\"", "<"=> "<", ">"=> ">", "&"=> "&", "\n" => " "}
|
439
481
|
mesg.gsub!(/(#{m.keys.join("|")})/) { m[$1] }
|
440
482
|
mesg
|
441
483
|
end
|
442
484
|
|
485
|
+
def check_replies
|
486
|
+
@prev_time_r ||= Time.now
|
487
|
+
api("statuses/replies").reverse_each do |s|
|
488
|
+
id = s["id"] || s["rid"]
|
489
|
+
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"]
|
494
|
+
mesg = generate_status_message(s)
|
495
|
+
|
496
|
+
tid = @tmap.push(s)
|
497
|
+
|
498
|
+
@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
|
504
|
+
end
|
505
|
+
@log.debug "@timeline.size = #{@timeline.size}"
|
506
|
+
@timeline = @timeline.last(100)
|
507
|
+
@prev_time_r = Time.now
|
508
|
+
end
|
509
|
+
|
443
510
|
def check_direct_messages
|
444
511
|
@prev_time_d ||= Time.now
|
445
512
|
api("direct_messages", {"since" => @prev_time_d.httpdate}).reverse_each do |s|
|
@@ -464,7 +531,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
464
531
|
prv_friends = @friends.map {|i| i["screen_name"] }
|
465
532
|
now_friends = friends.map {|i| i["screen_name"] }
|
466
533
|
|
467
|
-
#
|
534
|
+
# Twitter API bug?
|
468
535
|
return if !first && (now_friends.length - prv_friends.length).abs > 10
|
469
536
|
|
470
537
|
(now_friends - prv_friends).each do |join|
|
@@ -479,6 +546,23 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
479
546
|
end
|
480
547
|
end
|
481
548
|
|
549
|
+
def check_rate_limit
|
550
|
+
@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"]}"
|
554
|
+
@hourly_limit = rate_limit["hourly_limit"]
|
555
|
+
end
|
556
|
+
# rate_limit["remaining_hits"] < 1
|
557
|
+
# rate_limit["reset_time_in_seconds"] - Time.now.to_i
|
558
|
+
end
|
559
|
+
|
560
|
+
def freq(ratio)
|
561
|
+
ret = 3600 / (@hourly_limit * ratio).round
|
562
|
+
@log.debug "Frequency: #{ret}"
|
563
|
+
ret
|
564
|
+
end
|
565
|
+
|
482
566
|
def start_jabber(jid, pass)
|
483
567
|
@log.info "Logging-in with #{jid} -> jabber_bot_id: #{jabber_bot_id}"
|
484
568
|
@im = Jabber::Simple.new(jid, pass)
|
@@ -490,7 +574,6 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
490
574
|
@log.debug [msg.from, msg.body]
|
491
575
|
if msg.from.strip == jabber_bot_id
|
492
576
|
# Twitter -> 'id: msg'
|
493
|
-
# Wassr -> 'nick(id): msg'
|
494
577
|
body = msg.body.sub(/^(.+?)(?:\((.+?)\))?: /, "")
|
495
578
|
if Regexp.last_match
|
496
579
|
nick, id = Regexp.last_match.captures
|
@@ -531,14 +614,11 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
531
614
|
|
532
615
|
def api(path, q={})
|
533
616
|
ret = {}
|
534
|
-
|
535
|
-
"User-Agent"
|
536
|
-
"Authorization"
|
537
|
-
# "X-Twitter-Client" => api_source,
|
538
|
-
# "X-Twitter-Client-Version" => server_version,
|
539
|
-
# "X-Twitter-Client-URL" => "http://coderepos.org/share/browser/lang/ruby/misc/tig.rb",
|
617
|
+
headers = {
|
618
|
+
"User-Agent" => @user_agent,
|
619
|
+
"Authorization" => "Basic " + ["#{@real}:#{@pass}"].pack("m"),
|
540
620
|
}
|
541
|
-
|
621
|
+
headers["If-Modified-Since"] = q["since"] if q.key?("since")
|
542
622
|
|
543
623
|
q["source"] ||= api_source
|
544
624
|
q = q.inject([]) {|r,(k,v)| v.inject(r) {|r,i| r << "#{k}=#{URI.escape(i, /[^-.!~*'()\w]/n)}" } }.join("&")
|
@@ -556,9 +636,9 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
556
636
|
http.start do
|
557
637
|
case uri.path
|
558
638
|
when "/statuses/update.json", "/direct_messages/new.json"
|
559
|
-
ret = http.post(uri.request_uri, q,
|
639
|
+
ret = http.post(uri.request_uri, q, headers)
|
560
640
|
else
|
561
|
-
ret = http.get(uri.request_uri,
|
641
|
+
ret = http.get(uri.request_uri, headers)
|
562
642
|
end
|
563
643
|
end
|
564
644
|
|
@@ -571,7 +651,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
571
651
|
[]
|
572
652
|
when Net::HTTPBadRequest # 400
|
573
653
|
# exceeded the rate limitation
|
574
|
-
raise ApiFailed, "#{ret.code}: #{ret
|
654
|
+
raise ApiFailed, "#{ret.code}: #{ret.message}"
|
575
655
|
else
|
576
656
|
raise ApiFailed, "Server Returned #{ret.code} #{ret.message}"
|
577
657
|
end
|
@@ -609,14 +689,21 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
609
689
|
end
|
610
690
|
|
611
691
|
class TypableMap < Hash
|
612
|
-
Roma =
|
613
|
-
|
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|
|
693
|
+
case
|
694
|
+
when consonant.size > 1, consonant == "y"
|
695
|
+
%w|a u o|
|
696
|
+
when consonant == "q"
|
697
|
+
%w|a i e o|
|
698
|
+
else
|
699
|
+
%w|a i u e o|
|
700
|
+
end.map {|vowel| "#{consonant}#{vowel}" }
|
614
701
|
}.flatten
|
615
702
|
|
616
703
|
def initialize(size=1)
|
617
|
-
@seq
|
618
|
-
@map
|
619
|
-
@n
|
704
|
+
@seq = Roma
|
705
|
+
@map = {}
|
706
|
+
@n = 0
|
620
707
|
@size = size
|
621
708
|
end
|
622
709
|
|
@@ -663,7 +750,7 @@ if __FILE__ == $0
|
|
663
750
|
|
664
751
|
OptionParser.new do |parser|
|
665
752
|
parser.instance_eval do
|
666
|
-
self.banner
|
753
|
+
self.banner = <<-EOB.gsub(/^\t+/, "")
|
667
754
|
Usage: #{$0} [opts]
|
668
755
|
|
669
756
|
EOB
|
@@ -704,27 +791,27 @@ if __FILE__ == $0
|
|
704
791
|
opts[:logger] = Logger.new(opts[:log], "daily")
|
705
792
|
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
|
706
793
|
|
707
|
-
def daemonize(foreground=false)
|
708
|
-
trap("SIGINT") { exit! 0 }
|
709
|
-
trap("SIGTERM") { exit! 0 }
|
710
|
-
trap("SIGHUP") { exit! 0 }
|
711
|
-
return yield if $DEBUG || foreground
|
712
|
-
Process.fork do
|
713
|
-
Process.setsid
|
714
|
-
Dir.chdir "/"
|
715
|
-
File.open("/dev/null") {|f|
|
716
|
-
STDIN.reopen f
|
717
|
-
STDOUT.reopen f
|
718
|
-
STDERR.reopen f
|
719
|
-
}
|
720
|
-
yield
|
721
|
-
end
|
722
|
-
exit! 0
|
723
|
-
end
|
794
|
+
# def daemonize(foreground=false)
|
795
|
+
# trap("SIGINT") { exit! 0 }
|
796
|
+
# trap("SIGTERM") { exit! 0 }
|
797
|
+
# trap("SIGHUP") { exit! 0 }
|
798
|
+
# return yield if $DEBUG || foreground
|
799
|
+
# Process.fork do
|
800
|
+
# Process.setsid
|
801
|
+
# Dir.chdir "/"
|
802
|
+
# File.open("/dev/null") {|f|
|
803
|
+
# STDIN.reopen f
|
804
|
+
# STDOUT.reopen f
|
805
|
+
# STDERR.reopen f
|
806
|
+
# }
|
807
|
+
# yield
|
808
|
+
# end
|
809
|
+
# exit! 0
|
810
|
+
# end
|
724
811
|
|
725
|
-
daemonize(opts[:debug] || opts[:foreground]) do
|
812
|
+
# daemonize(opts[:debug] || opts[:foreground]) do
|
726
813
|
Net::IRC::Server.new(opts[:host], opts[:port], TwitterIrcGateway, opts).start
|
727
|
-
end
|
814
|
+
# end
|
728
815
|
end
|
729
816
|
|
730
817
|
# Local Variables:
|