net-irc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/ChangeLog +0 -0
  2. data/README +92 -0
  3. data/Rakefile +131 -0
  4. data/examples/lig.rb +276 -0
  5. data/examples/tig.rb +446 -0
  6. data/examples/wig.rb +132 -0
  7. data/lib/net/irc.rb +844 -0
  8. metadata +72 -0
data/examples/tig.rb ADDED
@@ -0,0 +1,446 @@
1
+ #!/usr/bin/env ruby -Ku
2
+ =begin
3
+
4
+ # tig.rb
5
+
6
+ Ruby version of TwitterIrcGateway
7
+ ( http://www.misuzilla.org/dist/net/twitterircgateway/ )
8
+
9
+
10
+ ## Client opts
11
+
12
+ Options specified by after irc realname.
13
+
14
+ Configuration example for tiarra ( http://coderepos.org/share/wiki/Tiarra ).
15
+
16
+ twitter {
17
+ host: localhost
18
+ port: 16668
19
+ name: username@example.com athack
20
+ password: password on twitter
21
+ in-encoding: utf8
22
+ out-encoding: utf8
23
+ }
24
+
25
+ ### athack
26
+
27
+ If `athack` client options specified,
28
+ all nick in join message is leading with @.
29
+
30
+ So if you complemente nicks (ex. irssi),
31
+ it's good for twitter like reply command (@nick).
32
+
33
+ In this case, you will see torrent of join messages after connected,
34
+ because NAMES list can't send @ leading nick (it interpreted op.)
35
+
36
+ ## Licence
37
+
38
+ Ruby's by cho45
39
+
40
+ =end
41
+
42
+ $LOAD_PATH << "lib"
43
+ $LOAD_PATH << "../lib"
44
+
45
+ $KCODE = "u" # json use this
46
+
47
+ require "rubygems"
48
+ require "net/http"
49
+ require "net/irc"
50
+ require "uri"
51
+ require "json"
52
+ require "socket"
53
+ require "time"
54
+ require "logger"
55
+ require "yaml"
56
+ require "pathname"
57
+ require "digest/md5"
58
+
59
+ Net::HTTP.version_1_2
60
+
61
+ class TwitterIrcGateway < Net::IRC::Server::Session
62
+ def server_name
63
+ "twittergw"
64
+ end
65
+
66
+ def server_version
67
+ "0.0.0"
68
+ end
69
+
70
+ def main_channel
71
+ "#twitter"
72
+ end
73
+
74
+ def api_base
75
+ @api_base ||= URI("http://twitter.com/")
76
+ end
77
+
78
+ class ApiFailed < StandardError; end
79
+
80
+ def initialize(*args)
81
+ super
82
+ @groups = {}
83
+ @channels = [] # join channels (groups)
84
+ @config = Pathname.new(ENV["HOME"]) + ".tig"
85
+ load_config
86
+ end
87
+
88
+ def on_user(m)
89
+ super
90
+ post @mask, JOIN, main_channel
91
+ @real, @opts = @real.split(/\s/)
92
+ @opts ||= []
93
+ @log.info "Client Options: #{@opts.inspect}"
94
+
95
+ @timeline = []
96
+ Thread.start do
97
+ loop do
98
+ begin
99
+ check_friends
100
+ rescue ApiFailed => e
101
+ @log.error e.inspect
102
+ rescue Exception => e
103
+ puts e
104
+ puts e.backtrace
105
+ end
106
+ sleep 10 * 60
107
+ end
108
+ end
109
+ sleep 3
110
+ Thread.start do
111
+ loop do
112
+ begin
113
+ check_timeline
114
+ # check_direct_messages
115
+ rescue ApiFailed => e
116
+ @log.error e.inspect
117
+ rescue Exception => e
118
+ puts e
119
+ puts e.backtrace
120
+ end
121
+ sleep 90
122
+ end
123
+ end
124
+ end
125
+
126
+ def on_privmsg(m)
127
+ retry_count = 3
128
+ ret = nil
129
+ target, message = *m.params
130
+ begin
131
+ if target =~ /^#/
132
+ ret = api("statuses/update.json", {"status" => message})
133
+ else
134
+ # direct message
135
+ ret = api("direct_messages/new.json", {"user" => target, "text" => message})
136
+ end
137
+ raise ApiFailed, "api failed" unless ret
138
+ log "Status Updated"
139
+ rescue => e
140
+ @log.error [retry_count, e.inspect].inspect
141
+ if retry_count > 0
142
+ retry_count -= 1
143
+ @log.debug "Retry to setting status..."
144
+ retry
145
+ else
146
+ log "Some Error Happened on Sending #{message}. #{e}"
147
+ end
148
+ end
149
+ end
150
+
151
+ def on_whois(m)
152
+ nick = m.params[0]
153
+ f = (@friends || []).find {|i| i["screen_name"] == nick }
154
+ if f
155
+ post nil, RPL_WHOISUSER, nick, nick, nick, api_base.host, "*", NKF.nkf("-j", "#{f["name"]} / #{f["description"]}")
156
+ post nil, RPL_WHOISSERVER, nick, api_base.host, api_base.to_s
157
+ post nil, RPL_WHOISIDLE, nick, "0", "seconds idle"
158
+ post nil, RPL_ENDOFWHOIS, nick, "End of WHOIS list"
159
+ else
160
+ post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
161
+ end
162
+ end
163
+
164
+ def on_who(m)
165
+ channel = m.params[0]
166
+ case
167
+ when channel == main_channel
168
+ # "<channel> <user> <host> <server> <nick>
169
+ # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
170
+ # :<hopcount> <real name>"
171
+ @friends.each do |f|
172
+ user = nick = f["screen_name"]
173
+ host = serv = api_base.host
174
+ real = f["name"]
175
+ post nil, RPL_WHOREPLY, channel, user, host, serv, nick, "H", "0 #{real}"
176
+ end
177
+ post nil, RPL_ENDOFWHO, channel
178
+ when @groups.key?(channel)
179
+ @groups[channel].each do |name|
180
+ f = @friends.find {|i| i["screen_name"] == name }
181
+ user = nick = f["screen_name"]
182
+ host = serv = api_base.host
183
+ real = f["name"]
184
+ post nil, RPL_WHOREPLY, channel, user, host, serv, nick, "H", "0 #{real}"
185
+ end
186
+ post nil, RPL_ENDOFWHO, channel
187
+ else
188
+ post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
189
+ end
190
+ end
191
+
192
+ def on_join(m)
193
+ channels = m.params[0].split(/\s*,\s*/)
194
+ channels.each do |channel|
195
+ next if channel == main_channel
196
+
197
+ @channels << channel
198
+ @channels.uniq!
199
+ post "#{@nick}!#{@nick}@#{api_base.host}", JOIN, channel
200
+ save_config
201
+ end
202
+ end
203
+
204
+ def on_part(m)
205
+ channel = m.params[0]
206
+ return if channel == main_channel
207
+
208
+ @channels.delete(channel)
209
+ post @nick, PART, channel, "Ignore group #{channel}, but setting is alive yet."
210
+ end
211
+
212
+ def on_invite(m)
213
+ nick, channel = *m.params
214
+ return if channel == main_channel
215
+
216
+ if (@friends || []).find {|i| i["screen_name"] == nick }
217
+ ((@groups[channel] ||= []) << nick).uniq!
218
+ post "#{nick}!#{nick}@#{api_base.host}", JOIN, channel
219
+ save_config
220
+ else
221
+ post ERR_NOSUCHNICK, nil, nick, "No such nick/channel"
222
+ end
223
+ end
224
+
225
+ def on_kick(m)
226
+ channel, nick, mes = *m.params
227
+ return if channel == main_channel
228
+
229
+ if (@friends || []).find {|i| i["screen_name"] == nick }
230
+ (@groups[channel] ||= []).delete(nick)
231
+ post nick, PART, channel
232
+ save_config
233
+ else
234
+ post ERR_NOSUCHNICK, nil, nick, "No such nick/channel"
235
+ end
236
+ end
237
+
238
+ private
239
+ def check_timeline
240
+ first = true unless @prev_time
241
+ @prev_time = Time.at(0) if first
242
+ api("statuses/friends_timeline.json", {"since" => [@prev_time.httpdate] }).reverse_each do |s|
243
+ nick = s["user"]["screen_name"]
244
+ mesg = s["text"]
245
+ time = Time.parse(s["created_at"]) rescue Time.now
246
+ m = { "&quot;" => "\"", "&lt;"=> "<", "&gt;"=> ">", "&amp;"=> "&", "\n" => " "}
247
+ mesg.gsub!(/(#{m.keys.join("|")})/) { m[$1] }
248
+
249
+ digest = Digest::MD5.hexdigest("#{nick}::#{mesg}")
250
+ unless @timeline.include?(digest)
251
+ @timeline << digest
252
+ @log.debug [nick, mesg, time].inspect
253
+ if nick == @nick # 自分のときは topic に
254
+ post nick, TOPIC, main_channel, mesg
255
+ else
256
+ message(nick, main_channel, mesg)
257
+ end
258
+ @groups.each do |channel,members|
259
+ if members.include?(nick)
260
+ message(nick, channel, mesg)
261
+ end
262
+ end
263
+ end
264
+ end
265
+ @timeline = @timeline.last(100)
266
+ @prev_time = Time.now
267
+ end
268
+
269
+ def check_direct_messages
270
+ first = true unless @prev_time_d
271
+ @prev_time_d = Time.now if first
272
+ api("direct_messages.json", {"since" => [@prev_time_d.httpdate] }).reverse_each do |s|
273
+ nick = s["sender_screen_name"]
274
+ mesg = s["text"]
275
+ time = Time.parse(s["created_at"])
276
+ @log.debug [nick, mesg, time].inspect
277
+ message(nick, @nick, mesg)
278
+ end
279
+ @prev_time_d = Time.now
280
+ end
281
+
282
+ def check_friends
283
+ first = true unless @friends
284
+ @friends ||= []
285
+ friends = api("statuses/friends.json")
286
+ if first && !@opts.include?("athack")
287
+ @friends = friends
288
+ post nil, RPL_NAMREPLY, server_name, @nick, "=", main_channel, @friends.map{|i| i["screen_name"] }.join(" ")
289
+ post nil, RPL_ENDOFNAMES, server_name, @nick, main_channel, "End of NAMES list"
290
+ else
291
+ prv_friends = @friends.map {|i| i["screen_name"] }
292
+ now_friends = friends.map {|i| i["screen_name"] }
293
+ (now_friends - prv_friends).each do |join|
294
+ join = "@#{join}" if @opts.include?("athack")
295
+ post "#{join}!#{join}@#{api_base.host}", JOIN, main_channel
296
+ end
297
+ (prv_friends - now_friends).each do |part|
298
+ part = "@#{part}" if @opts.include?("athack")
299
+ post "#{part}!#{part}@#{api_base.host}", PART, main_channel, ""
300
+ end
301
+ @friends = friends
302
+ end
303
+ end
304
+
305
+ def save_config
306
+ config = {
307
+ :channels => @channels,
308
+ :groups => @groups,
309
+ }
310
+ @config.open("w") do |f|
311
+ YAML.dump(config, f)
312
+ end
313
+ end
314
+
315
+ def load_config
316
+ @config.open do |f|
317
+ config = YAML.load(f)
318
+ @channels = config[:channels]
319
+ @groups = config[:groups]
320
+ end
321
+ rescue Errno::ENOENT
322
+ end
323
+
324
+ def api(path, q={})
325
+ ret = {}
326
+ q["source"] = "tigrb"
327
+ q = q.inject([]) {|r,(k,v)| v.inject(r) {|r,i| r << "#{k}=#{URI.escape(i, /[^-.!~*'()\w]/n)}" } }.join("&")
328
+ uri = api_base + "/#{path}?#{q}"
329
+ @log.debug uri.inspect
330
+ Net::HTTP.start(uri.host, uri.port) do |http|
331
+ header = {
332
+ 'Authorization' => "Basic " + ["#{@real}:#{@pass}"].pack("m"),
333
+ }
334
+ case path
335
+ when "statuses/update.json", "direct_messages/new.json"
336
+ ret = http.post(uri.request_uri, q, header)
337
+ else
338
+ ret = http.get(uri.request_uri, header)
339
+ end
340
+ end
341
+ @log.debug ret.inspect
342
+ case ret.code
343
+ when "200"
344
+ JSON.parse(ret.body)
345
+ when "304"
346
+ []
347
+ else
348
+ raise ApiFailed, "Server Returned #{ret.code}"
349
+ end
350
+ rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
351
+ raise ApiFailed, e.inspect
352
+ end
353
+
354
+ def message(sender, target, str)
355
+ # str.gsub!(/&#(x)?([0-9a-f]+);/i) do |m|
356
+ # [$1 ? $2.hex : $2.to_i].pack("U")
357
+ # end
358
+ str = untinyurl(str)
359
+ sender = "#{sender}!#{sender}@#{api_base.host}"
360
+ post sender, PRIVMSG, target, str
361
+ end
362
+
363
+ def log(str)
364
+ str.gsub!(/\n/, " ")
365
+ post server_name, NOTICE, main_channel, str
366
+ end
367
+
368
+ def untinyurl(text)
369
+ text.gsub(%r|http://tinyurl.com/[0-9a-z=]+|i) {|m|
370
+ uri = URI(m)
371
+ Net::HTTP.start(uri.host, uri.port) {|http|
372
+ http.head(uri.request_uri)["Location"]
373
+ }
374
+ }
375
+ end
376
+ end
377
+
378
+ if __FILE__ == $0
379
+ require "optparse"
380
+
381
+ opts = {
382
+ :port => 16668,
383
+ :host => "localhost",
384
+ :debug => false,
385
+ :log => nil,
386
+ :debug => false,
387
+ }
388
+
389
+ OptionParser.new do |parser|
390
+ parser.instance_eval do
391
+ self.banner = <<-EOB.gsub(/^\t+/, "")
392
+ Usage: #{$0} [opts]
393
+
394
+ EOB
395
+
396
+ separator ""
397
+
398
+ separator "Options:"
399
+ on("-p", "--port [PORT=#{opts[:port]}]", "listen port number") do |port|
400
+ opts[:port] = port
401
+ end
402
+
403
+ on("-h", "--host [HOST=#{opts[:host]}]", "listen host") do |host|
404
+ opts[:host] = host
405
+ end
406
+
407
+ on("-l", "--log LOG", "log file") do |log|
408
+ opts[:log] = log
409
+ end
410
+
411
+ on("--debug", "Enable debug mode") do |debug|
412
+ opts[:log] = $stdout
413
+ opts[:debug] = true
414
+ end
415
+
416
+ parse!(ARGV)
417
+ end
418
+ end
419
+
420
+ opts[:logger] = Logger.new(opts[:log], "daily")
421
+ opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
422
+
423
+ def daemonize(debug=false)
424
+ return yield if $DEBUG || debug
425
+ Process.fork do
426
+ Process.setsid
427
+ Dir.chdir "/"
428
+ trap("SIGINT") { exit! 0 }
429
+ trap("SIGTERM") { exit! 0 }
430
+ trap("SIGHUP") { exit! 0 }
431
+ File.open("/dev/null") {|f|
432
+ STDIN.reopen f
433
+ STDOUT.reopen f
434
+ STDERR.reopen f
435
+ }
436
+ yield
437
+ end
438
+ exit! 0
439
+ end
440
+
441
+ daemonize(opts[:debug]) do
442
+ Net::IRC::Server.new(opts[:host], opts[:port], TwitterIrcGateway, opts).start
443
+ end
444
+ end
445
+
446
+
data/examples/wig.rb ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env ruby
2
+ =begin
3
+
4
+ # wig.rb
5
+
6
+ Wassr IRC Gateway
7
+
8
+
9
+ ## Client opts
10
+
11
+ Options specified by after irc realname.
12
+
13
+ Configuration example for tiarra ( http://coderepos.org/share/wiki/Tiarra ).
14
+
15
+ wassr {
16
+ host: localhost
17
+ port: 16670
18
+ name: username@example.com athack
19
+ password: password on wassr
20
+ in-encoding: utf8
21
+ out-encoding: utf8
22
+ }
23
+
24
+ ### athack
25
+
26
+ If `athack` client options specified,
27
+ all nick in join message is leading with @.
28
+
29
+ So if you complemente nicks (ex. irssi),
30
+ it's good for twitter like reply command (@nick).
31
+
32
+ In this case, you will see torrent of join messages after connected,
33
+ because NAMES list can't send @ leading nick (it interpreted op.)
34
+
35
+ ## Licence
36
+
37
+ Ruby's by cho45
38
+
39
+ =end
40
+
41
+ $LOAD_PATH << File.dirname(__FILE__)
42
+
43
+ require "tig.rb"
44
+
45
+ class WassrIrcGateway < TwitterIrcGateway
46
+ def server_name
47
+ "wassrgw"
48
+ end
49
+
50
+ def server_version
51
+ "0.0.0"
52
+ end
53
+
54
+ def main_channel
55
+ "#wassr"
56
+ end
57
+
58
+ def api_base
59
+ @api_base ||= URI("http://api.wassr.jp/")
60
+ end
61
+ end
62
+
63
+ if __FILE__ == $0
64
+ require "optparse"
65
+
66
+ opts = {
67
+ :port => 16670,
68
+ :host => "localhost",
69
+ :debug => false,
70
+ :log => nil,
71
+ :debug => false,
72
+ }
73
+
74
+ OptionParser.new do |parser|
75
+ parser.instance_eval do
76
+ self.banner = <<-EOB.gsub(/^\t+/, "")
77
+ Usage: #{$0} [opts]
78
+
79
+ EOB
80
+
81
+ separator ""
82
+
83
+ separator "Options:"
84
+ on("-p", "--port [PORT=#{opts[:port]}]", "listen port number") do |port|
85
+ opts[:port] = port
86
+ end
87
+
88
+ on("-h", "--host [HOST=#{opts[:host]}]", "listen host") do |host|
89
+ opts[:host] = host
90
+ end
91
+
92
+ on("-l", "--log LOG", "log file") do |log|
93
+ opts[:log] = log
94
+ end
95
+
96
+ on("--debug", "Enable debug mode") do |debug|
97
+ opts[:log] = $stdout
98
+ opts[:debug] = true
99
+ end
100
+
101
+ parse!(ARGV)
102
+ end
103
+ end
104
+
105
+ opts[:logger] = Logger.new(opts[:log], "daily")
106
+ opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
107
+
108
+ def daemonize(debug=false)
109
+ return yield if $DEBUG || debug
110
+ Process.fork do
111
+ Process.setsid
112
+ Dir.chdir "/"
113
+ trap("SIGINT") { exit! 0 }
114
+ trap("SIGTERM") { exit! 0 }
115
+ trap("SIGHUP") { exit! 0 }
116
+ File.open("/dev/null") {|f|
117
+ STDIN.reopen f
118
+ STDOUT.reopen f
119
+ STDERR.reopen f
120
+ }
121
+ yield
122
+ end
123
+ exit! 0
124
+ end
125
+
126
+ daemonize(opts[:debug]) do
127
+ Net::IRC::Server.new(opts[:host], opts[:port], WassrIrcGateway, opts).start
128
+ end
129
+ end
130
+
131
+
132
+