net-irc 0.0.1 → 0.0.2

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