net-irc 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -0,0 +1,27 @@
1
+ ChangeLog of http://svn.coderepos.org/share/lang/ruby/net-irc/trunk
2
+
3
+ 2008-02-01 cho45
4
+
5
+ * [bug] @5986:
6
+ Fix to destroy closed stream.
7
+
8
+ 2008-01-31 cho45
9
+
10
+ * [new] @5939:
11
+ Add client example.
12
+
13
+ * [new] @5929:
14
+ Update tests.
15
+ Allow lame prefix in RPL_WELCOME (like freenode)
16
+
17
+ 2008-01-29 cho45
18
+
19
+ * [bug] @5846:
20
+ athack つかわないときの処理がもろに間違ってた。
21
+
22
+ * [bug] @5843:
23
+ Net::IRC::Server の修正に追従できていなかった
24
+
25
+ * [release] @5832:
26
+ Release 0.0.1
27
+
data/Rakefile CHANGED
@@ -41,6 +41,7 @@ task :package => [:clean]
41
41
  Spec::Rake::SpecTask.new do |t|
42
42
  t.spec_opts = ['--options', "spec/spec.opts"]
43
43
  t.spec_files = FileList['spec/*_spec.rb']
44
+ t.rcov = true
44
45
  end
45
46
 
46
47
  spec = Gem::Specification.new do |s|
@@ -65,7 +66,7 @@ spec = Gem::Specification.new do |s|
65
66
  #s.required_ruby_version = '>= 1.8.2'
66
67
 
67
68
  s.files = %w(README ChangeLog Rakefile) +
68
- Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
69
+ Dir.glob("{bin,doc,spec,test,lib,templates,generator,extras,website,script}/**/*") +
69
70
  Dir.glob("ext/**/*.{h,c,rb}") +
70
71
  Dir.glob("examples/**/*.rb") +
71
72
  Dir.glob("tools/*.rb")
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+
4
+ $LOAD_PATH << "lib"
5
+ $LOAD_PATH << "../lib"
6
+
7
+ require "rubygems"
8
+ require "net/irc"
9
+
10
+ require "pp"
11
+
12
+ class SimpleClient < Net::IRC::Client
13
+ def initialize(*args)
14
+ super
15
+ end
16
+ end
17
+
18
+ SimpleClient.new("foobar", "6667", {
19
+ :nick => "foobartest",
20
+ :user => "foobartest",
21
+ :real => "foobartest",
22
+ }).start
23
+
data/examples/lig.rb CHANGED
@@ -1,22 +1,82 @@
1
1
  #!/usr/bin/env ruby
2
+ =begin
2
3
 
4
+ # lig.rb
5
+
6
+ Lingr IRC Gateway - IRC Gateway to Lingr ( http://www.lingr.com/ )
7
+
8
+ ## Launch
9
+
10
+ $ ruby lig.rb # daemonized
11
+
12
+ If you want to help:
13
+
14
+ $ ruby lig.rb --help
15
+ Usage: examples/lig.rb [opts]
16
+
17
+
18
+ Options:
19
+ -p, --port [PORT=16669] port number to listen
20
+ -h, --host [HOST=localhost] host name or IP address to listen
21
+ -l, --log LOG log file
22
+ -a, --api_key API_KEY Your api key on Lingr
23
+ --debug Enable debug mode
24
+
25
+ ## Configuration
26
+
27
+ Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
28
+
29
+ lingr {
30
+ host: localhost
31
+ port: 16669
32
+ name: username@example.com (Email on Lingr)
33
+ password: password on Lingr
34
+ in-encoding: utf8
35
+ out-encoding: utf8
36
+ }
37
+
38
+ Set your email as IRC 'real name' field, and password as server password.
39
+ This does not allow anonymous connection to Lingr.
40
+ You must create a account on Lingr and get API key (ask it first time).
41
+
42
+ ## Client
43
+
44
+ This gateway sends multibyte nicknames at Lingr rooms as-is.
45
+ So you should use a client which treats it correctly.
46
+
47
+ Recommended:
48
+
49
+ * LimeChat for OSX ( http://limechat.sourceforge.net/ )
50
+ * Irssi ( http://irssi.org/ )
51
+ * (gateway) Tiarra ( http://coderepos.org/share/wiki/Tiarra )
52
+
53
+ ## Nickname/Mask
54
+
55
+ nick -> nickname in a room.
56
+ o_id -> occupant_id (unique id in a room)
57
+ u_id -> user_id (unique user id in Lingr)
58
+
59
+ * Anonymous User: <nick>|<o_id>!anon@lingr.com
60
+ * Logged-in User: <nick>|<o_id>!<u_id>@lingr.com
61
+ * Your: <nick>|<u_id>!<u_id>@lingr.com
62
+
63
+ So you can see some nicknames in same user, but it is needed for
64
+ nickname management on client.
65
+
66
+ (Lingr allows different nicknames between rooms in a same user, but IRC not)
67
+
68
+ ## Licence
69
+
70
+ Ruby's by cho45
71
+
72
+ =end
73
+
74
+ $LOAD_PATH << File.dirname(__FILE__)
3
75
  $LOAD_PATH << "lib"
4
76
  $LOAD_PATH << "../lib"
5
77
 
6
78
  require "rubygems"
7
-
8
- # http://svn.lingr.com/api/toolkits/ruby/infoteria/api_client.rb
9
- begin
10
- require "api_client"
11
- rescue LoadError
12
- require "net/http"
13
- require "uri"
14
- File.open("api_client.rb", "w") do |f|
15
- f.puts Net::HTTP.get(URI("http://svn.lingr.com/api/toolkits/ruby/infoteria/api_client.rb"))
16
- end
17
- require "api_client"
18
- end
19
-
79
+ require "lingr"
20
80
  require "net/irc"
21
81
  require "pit"
22
82
 
@@ -37,44 +97,86 @@ class LingrIrcGateway < Net::IRC::Server::Session
37
97
 
38
98
  def on_user(m)
39
99
  super
40
- @real, @copts = @real.split(/\s/)
100
+ @real, @copts = @real.split(/\s+/)
41
101
  @copts ||= []
42
102
 
103
+ # Tiarra sends prev nick when reconnects.
104
+ @nick.sub!(/\|.+$/, "")
105
+
43
106
  log "Hello #{@nick}, this is Lingr IRC Gateway."
44
107
  log "Client Option: #{@copts.join(", ")}"
45
108
  @log.info "Client Option: #{@copts.join(", ")}"
46
109
  @log.info "Client initialization is completed."
47
110
 
48
- @lingr = Lingr::ApiClient.new(@opts.api_key)
111
+ @lingr = Lingr::Client.new(@opts.api_key)
49
112
  @lingr.create_session('human')
50
113
  @lingr.login(@real, @pass)
51
- @user_info = @lingr.get_user_info[:response]
114
+ @user_info = @lingr.get_user_info
115
+
116
+ prefix = make_ids(@user_info)
117
+ @user_info["prefix"] = prefix
118
+ post @prefix, NICK, prefix.nick
52
119
  end
53
120
 
54
121
  def on_privmsg(m)
55
122
  target, message = *m.params
56
- @lingr.say(@channels[target.downcase][:ticket], message)
123
+ if @channels.key?(target.downcase)
124
+ @lingr.say(@channels[target.downcase][:ticket], message)
125
+ else
126
+ post nil, ERR_NOSUCHNICK, @user_info["prefix"].nick, target, "No such nick/channel"
127
+ end
128
+ rescue Lingr::Client::APIError => e
129
+ log "Error: #{e.code}: #{e.message}"
130
+ log "Coundn't say to #{channel}."
57
131
  end
58
132
 
59
133
  def on_whois(m)
60
134
  nick = m.params[0]
61
- # TODO
135
+ chan = nil
136
+ info = nil
137
+
138
+ @channels.each do |k, v|
139
+ if v[:users].key?(nick)
140
+ chan = k
141
+ info = v[:users][nick]
142
+ break
143
+ end
144
+ end
145
+
146
+ if chan
147
+ prefix = info["prefix"]
148
+ real_name = info["description"].to_s
149
+ server_info = "Lingr: type:#{info["client_type"]} source:#{info["source"]}"
150
+ channels = [info["client_type"] == "human" ? "@#{chan}" : chan]
151
+ me = @user_info["nick"]
152
+
153
+ post nil, RPL_WHOISUSER, me.nick, prefix.nick, prefix.user, prefix.host, "*", real_name
154
+ post nil, RPL_WHOISSERVER, me.nick, prefix.nick, prefix.host, server_info
155
+ # post nil, RPL_WHOISOPERATOR, me.nick, prefix.nick, "is an IRC operator"
156
+ # post nil, RPL_WHOISIDLE, me.nick, prefix.nick, idle, "seconds idle"
157
+ post nil, RPL_WHOISCHANNELS, me.nick, prefix.nick, channels.join(" ")
158
+ post nil, RPL_ENDOFWHOIS, me.nick, prefix.nick, "End of WHOIS list"
159
+ else
160
+ post nil, ERR_NOSUCHNICK, me.nick, nick, "No such nick/channel"
161
+ end
62
162
  end
63
163
 
64
164
  def on_who(m)
65
165
  channel = m.params[0]
66
- info = @channels[channel.downcase]
67
- res = @lingr.get_room_info(info[:chan_id], nil, info[:password])
68
- if res[:succeeded]
69
- res = res[:response]
70
- res["occupants"].each do |o|
71
- u_id, o_id, nick = *make_ids(o)
72
- post nil, RPL_WHOREPLY, channel, o_id, "lingr.com", "lingr.com", nick, "H", "0 #{o["description"].to_s.gsub(/\s+/, " ")}"
73
- end
74
- post nil, RPL_ENDOFWHO, channel
75
- else
76
- log "Maybe gateway don't know password for channel #{channel}. Please part and join."
166
+ return unless channel
167
+
168
+ info = @channels[channel.downcase]
169
+ me = @user_info["prefix"]
170
+ res = @lingr.get_room_info(info[:chan_id], nil, info[:password])
171
+ res["occupants"].each do |o|
172
+ next unless o["nickname"]
173
+ u_id, o_id, prefix = *make_ids(o, true)
174
+ op = (o["client_type"] == "human") ? "@" : ""
175
+ post nil, RPL_WHOREPLY, me.nick, channel, o_id, "lingr.com", "lingr.com", prefix.nick, "H*#{op}", "0 #{o["description"].to_s.gsub(/\s+/, " ")}"
77
176
  end
177
+ post nil, RPL_ENDOFWHO, me.nick, channel
178
+ rescue Lingr::Client::APIError => e
179
+ log "Maybe gateway don't know password for channel #{channel}. Please part and join."
78
180
  end
79
181
 
80
182
  def on_join(m)
@@ -82,28 +184,37 @@ class LingrIrcGateway < Net::IRC::Server::Session
82
184
  password = m.params[1]
83
185
  channels.each do |channel|
84
186
  next if @channels.key? channel.downcase
85
- @log.debug "Enter room -> #{channel}"
86
- res = @lingr.enter_room(channel.sub(/^#/, ""), @nick, password)
87
- if res[:succeeded]
88
- res[:response]["password"] = password
89
- o_id = res[:response]["occupant_id"]
90
- post "#{@nick}!#{o_id}@lingr.com", JOIN, channel
91
- create_observer(channel, res[:response])
92
- else
93
- log "Error: #{(res && rese['error']) ? res[:response]["error"]["message"] : "socket error"}"
187
+ begin
188
+ @log.debug "Enter room -> #{channel}"
189
+ res = @lingr.enter_room(channel.sub(/^#/, ""), @nick, password)
190
+ res["password"] = password
191
+
192
+ create_observer(channel, res)
193
+ rescue Lingr::Client::APIError => e
194
+ log "Error: #{e.code}: #{e.message}"
195
+ log "Coundn't join to #{channel}."
196
+ rescue Exception => e
197
+ @log.error e.inspect
198
+ e.backtrace.each do |l|
199
+ @log.error "\t#{l}"
200
+ end
94
201
  end
95
202
  end
96
203
  end
97
204
 
98
205
  def on_part(m)
99
206
  channel = m.params[0]
100
- info = @channels[channel].downcase
207
+ info = @channels[channel.downcase]
208
+ prefix = @user_info["prefix"]
101
209
 
102
210
  if info
103
211
  info[:observer].kill
104
212
  @lingr.exit_room(info[:ticket])
105
213
  @channels.delete(channel.downcase)
106
- post @nick, PART, channel, "Parted"
214
+
215
+ post prefix, PART, channel, "Parted"
216
+ else
217
+ post nil, ERR_NOSUCHCHANNEL, prefix.nick, channel, "No such channel"
107
218
  end
108
219
  end
109
220
 
@@ -111,85 +222,173 @@ class LingrIrcGateway < Net::IRC::Server::Session
111
222
 
112
223
  def create_observer(channel, response)
113
224
  Thread.start(channel, response) do |chan, res|
114
- begin
115
- post server_name, TOPIC, chan, "#{res["room"]["url"]} #{res["room"]["description"]}"
116
- @channels[chan.downcase] = {
117
- :ticket => res["ticket"],
118
- :counter => res["counter"],
119
- :o_id => res["occupant_id"],
120
- :chan_id => res["room"]["id"],
121
- :password => res["password"],
122
- :hcounter => 0,
123
- :observer => Thread.current,
124
- }
125
- first = true
126
- while true
127
- info = @channels[chan.downcase]
225
+ myprefix = @user_info["prefix"]
226
+ post server_name, TOPIC, chan, "#{res["room"]["url"]} #{res["room"]["description"]}"
227
+ @channels[chan.downcase] = {
228
+ :ticket => res["ticket"],
229
+ :counter => res["counter"],
230
+ :o_id => res["occupant_id"],
231
+ :chan_id => res["room"]["id"],
232
+ :password => res["password"],
233
+ :users => res["occupants"].reject {|i| i["nickname"].nil? }.inject({}) {|r,i|
234
+ i["prefix"] = make_ids(i)
235
+ r.update(i["prefix"].nick => i)
236
+ },
237
+ :hcounter => 0,
238
+ :observer => Thread.current,
239
+ }
240
+ post myprefix, JOIN, channel
241
+ post server_name, MODE, channel, "+o", myprefix.nick
242
+ post nil, RPL_NAMREPLY, myprefix.nick, "=", chan, @channels[chan.downcase][:users].map{|k,v|
243
+ v["client_type"] == "human" ? "@#{k}" : k
244
+ }.join(" ")
245
+ post nil, RPL_ENDOFNAMES, myprefix.nick, chan, "End of NAMES list"
246
+
247
+ first = true
248
+ info = @channels[chan.downcase]
249
+ while true
250
+ begin
128
251
  res = @lingr.observe_room info[:ticket], info[:counter]
129
- @log.debug "observe_room returned"
130
- if res[:succeeded]
131
- info[:counter] = res[:response]["counter"] if res[:response]["counter"]
132
- (res[:response]["messages"] || []).each do |m|
133
- next if m["id"].to_i <= info[:hcounter]
134
-
135
- u_id, o_id, nick = *make_ids(m)
136
-
137
- case m["type"]
138
- when "user"
139
- if first
140
- post nick, NOTICE, chan, m["text"]
141
- else
142
- post nick, PRIVMSG, chan, m["text"] unless info[:o_id] == o_id
252
+ @log.debug "observe_room<#{chan}> returned"
253
+
254
+ info[:counter] = res["counter"] if res["counter"]
255
+
256
+ (res["messages"] || []).each do |m|
257
+ next if m["id"].to_i <= info[:hcounter]
258
+
259
+ u_id, o_id, prefix = *make_ids(m, true)
260
+
261
+ case m["type"]
262
+ when "user"
263
+ if first
264
+ post prefix, NOTICE, chan, m["text"]
265
+ else
266
+ # Don't send my messages.
267
+ unless info[:o_id] == o_id
268
+ post prefix, PRIVMSG, chan, m["text"]
143
269
  end
144
- when "private"
145
- # TODO
146
- post nick, PRIVMSG, chan, "\x01ACTION Sent private: #{m["text"]}\x01" unless info[:o_id] == o_id
147
- when "system:enter"
148
- post "#{nick}!#{o_id}@lingr.com", JOIN, chan unless nick == @nick
149
- when "system:leave"
150
- #post "#{nick}!#{o_id}@lingr.com", PART, chan unless nick == @nick
151
- when "system:nickname_change"
152
- post nick, NOTICE, chan, m["text"]
153
- when "system:broadcast"
154
- post nil, NOTICE, chan, m["text"]
155
270
  end
271
+ when "private"
272
+ # TODO not sent from lingr?
273
+ post prefix, PRIVMSG, chan, ctcp_encoding("ACTION Sent private: #{m["text"]}")
274
+
275
+ # system:{enter,leave,nickname_changed} should not be used for nick management.
276
+ # when "system:enter"
277
+ # post prefix, PRIVMSG, chan, ctcp_encoding("ACTION #{m["text"]}")
278
+ # when "system:leave"
279
+ # post prefix, PRIVMSG, chan, ctcp_encoding("ACTION #{m["text"]}")
280
+ # when "system:nickname_change"
281
+ # post prefix, PRIVMSG, chan, ctcp_encoding("ACTION #{m["text"]}")
282
+ when "system:broadcast"
283
+ post "system.broadcast", NOTICE, chan, m["text"]
284
+ end
156
285
 
157
- info[:hcounter] = m["id"].to_i if m["id"]
286
+ info[:hcounter] = m["id"].to_i if m["id"]
287
+ end
288
+
289
+ if res["occupants"]
290
+ enter = [], leave = []
291
+ newusers = res["occupants"].reject {|i| i["nickname"].nil? }.inject({}) {|r,i|
292
+ i["prefix"] = make_ids(i)
293
+ r.update(i["prefix"].nick => i)
294
+ }
295
+
296
+
297
+ nickchange = newusers.inject({:new => [], :old => []}) {|r,(k,new)|
298
+ old = info[:users].find {|l,old|
299
+ # same occupant_id and different nickname
300
+ # when nickname was changed and when un-authed user promoted to authed user.
301
+ new["prefix"] != old["prefix"] && new["id"] == old["id"]
302
+ }
303
+ if old
304
+ old = old[1]
305
+ post old["prefix"], NICK, new["prefix"].nick
306
+ r[:old] << old["prefix"].nick
307
+ r[:new] << new["prefix"].nick
308
+ end
309
+ r
310
+ }
311
+
312
+ entered = newusers.keys - info[:users].keys - nickchange[:new]
313
+ leaved = info[:users].keys - newusers.keys - entered - nickchange[:old]
314
+
315
+ leaved.each do |leave|
316
+ leave = info[:users][leave]
317
+ post leave["prefix"], PART, chan, ""
158
318
  end
159
319
 
160
- if res["occupants"]
161
- res["occupants"].each do |o|
162
- # new_roster[o["id"]] = o["nickname"]
163
- if o["nickname"]
164
- nick = o["nickname"]
165
- o_id = m["occupant_id"]
166
- post "#{nick}!#{o_id}@lingr.com", JOIN, chan
167
- end
320
+ entered.each do |enter|
321
+ enter = newusers[enter]
322
+ prefix = enter["prefix"]
323
+ post prefix, JOIN, chan
324
+ if enter["client_type"] == "human"
325
+ post server_name, MODE, chan, "+o", prefix.nick
168
326
  end
169
327
  end
170
- else
171
- @log.debug "observe failed : #{res[:response].inspect}"
172
- log "Error: #{(res && res['error']) ? res[:response]["error"]["message"] : "socket error"}"
328
+
329
+ info[:users] = newusers
173
330
  end
331
+
332
+
174
333
  first = false
334
+ rescue Lingr::Client::APIError => e
335
+ case e.code
336
+ when 100
337
+ @log.fatal "BUG: API returns invalid HTTP method"
338
+ exit 1
339
+ when 102
340
+ @log.error "BUG: API returns invalid session. Prompt the client to reconnect."
341
+ finish
342
+ when 104
343
+ @log.fatal "BUG: API returns invalid response format. JSON is unsupported?"
344
+ exit 1
345
+ when 109
346
+ @log.error "BUG: API returns invalid ticket. Part this channel..."
347
+ on_part(Message.new("", PART, [chan, res["error"]["message"]]))
348
+ when 114
349
+ @log.fatal "BUG: API returns no counter parameter."
350
+ exit 1
351
+ when 120
352
+ @log.error "BUG: API returns invalid encoding. But continues."
353
+ when 122
354
+ @log.fatal "BUG: API returns repeated counter"
355
+ exit 1
356
+ else
357
+ # may be socket error?
358
+ @log.debug "observe failed : #{res.inspect}"
359
+ log "Error: #{e.code}: #{e.message}"
360
+ end
361
+ rescue Exception => e
362
+ @log.error e.inspect
363
+ e.backtrace.each do |l|
364
+ @log.error "\t#{l}"
365
+ end
175
366
  end
176
- rescue Exception => e
177
- puts e
178
- puts e.backtrace
367
+ sleep 1
179
368
  end
180
369
  end
181
370
  end
182
371
 
183
372
  def log(str)
184
373
  str.gsub!(/\s/, " ")
185
- post nil, NOTICE, @nick, str
374
+ begin
375
+ post nil, NOTICE, @user_info["prefix"].nick, str
376
+ rescue
377
+ post nil, NOTICE, @nick, str
378
+ end
186
379
  end
187
380
 
188
- def make_ids(o)
189
- u_id = o["user_id"]
190
- o_id = o["occupant_id"] || o["id"]
191
- nick = o["nickname"].gsub(/\s+/, "") + "^#{u_id || "anon"}"
192
- [u_id, o_id, nick]
381
+ def make_ids(o, ext=false)
382
+ u_id = o["user_id"] || "anon"
383
+ o_id = o["occupant_id"] || o["id"]
384
+ nick = (o["default_nickname"] || o["nickname"]).gsub(/\s+/, "")
385
+ if o["user_id"] == @user_info["user_id"]
386
+ nick << "|#{o["user_id"]}"
387
+ else
388
+ nick << "|#{o["user_id"] ? o_id : "_"+o_id}"
389
+ end
390
+ pref = Prefix.new("#{nick}!#{u_id}@lingr.com")
391
+ ext ? [u_id, o_id, pref] : pref
193
392
  end
194
393
  end
195
394
 
@@ -200,11 +399,10 @@ if __FILE__ == $0
200
399
  require "pit"
201
400
 
202
401
  opts = {
203
- :port => 16669,
204
- :host => "localhost",
205
- :debug => false,
206
- :log => nil,
207
- :debug => false,
402
+ :port => 16669,
403
+ :host => "localhost",
404
+ :log => nil,
405
+ :debug => false,
208
406
  }
209
407
 
210
408
  OptionParser.new do |parser|
@@ -217,11 +415,11 @@ if __FILE__ == $0
217
415
  separator ""
218
416
 
219
417
  separator "Options:"
220
- on("-p", "--port [PORT=#{opts[:port]}]", "listen port number") do |port|
418
+ on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
221
419
  opts[:port] = port
222
420
  end
223
421
 
224
- on("-h", "--host [HOST=#{opts[:host]}]", "listen host") do |host|
422
+ on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
225
423
  opts[:host] = host
226
424
  end
227
425
 
@@ -264,7 +462,7 @@ if __FILE__ == $0
264
462
  end
265
463
 
266
464
  opts[:api_key] = Pit.get("lig.rb", :require => {
267
- "api_key" => "API key of lingr"
465
+ "api_key" => "API key of Lingr"
268
466
  })["api_key"] unless opts[:api_key]
269
467
 
270
468
  daemonize(opts[:debug]) do