net-irc 0.0.1

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.
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
+