tkellem 0.8.7 → 0.8.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Rakefile +0 -23
- data/lib/tkellem/bouncer.rb +3 -14
- data/lib/tkellem/bouncer_connection.rb +1 -1
- data/lib/tkellem/irc_message.rb +4 -2
- data/lib/tkellem/irc_server.rb +86 -14
- data/lib/tkellem/models/host.rb +4 -0
- data/lib/tkellem/plugins/push_service.rb +2 -1
- data/lib/tkellem/version.rb +1 -1
- data/spec/irc_message_spec.rb +8 -0
- data/spec/irc_server_spec.rb +4 -3
- data/tkellem.gemspec +0 -4
- metadata +10 -43
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -11,26 +11,3 @@ RSpec::Core::RakeTask.new(:rcov) do |t|
|
|
11
11
|
t.rcov = true
|
12
12
|
t.rcov_opts = ["--exclude", "spec,gems/,rubygems/"]
|
13
13
|
end
|
14
|
-
|
15
|
-
require 'yard'
|
16
|
-
YARD::Rake::YardocTask.new(:doc) do |t|
|
17
|
-
version = Tkellem::VERSION
|
18
|
-
t.options = ["--title", "tkellem #{version}", "--files", "LICENSE,README.md"]
|
19
|
-
end
|
20
|
-
|
21
|
-
begin
|
22
|
-
require 'jeweler'
|
23
|
-
Jeweler::Tasks.new do |gem|
|
24
|
-
gem.name = 'tkellem'
|
25
|
-
gem.summary = 'IRC bouncer with multi-client support'
|
26
|
-
gem.email = 'brian@codekitchen.net'
|
27
|
-
gem.homepage = 'http://github.com/codekitchen/tkellem'
|
28
|
-
gem.authors = ['Brian Palmer']
|
29
|
-
gem.add_dependency 'eventmachine'
|
30
|
-
gem.executables = %w(tkellem)
|
31
|
-
end
|
32
|
-
|
33
|
-
Jeweler::GemcutterTasks.new
|
34
|
-
rescue LoadError
|
35
|
-
# om nom nom
|
36
|
-
end
|
data/lib/tkellem/bouncer.rb
CHANGED
@@ -183,7 +183,7 @@ class Bouncer
|
|
183
183
|
@conn = nil
|
184
184
|
@connected = false
|
185
185
|
@connected_at = nil
|
186
|
-
@active_conns.each { |c,s| c.
|
186
|
+
@active_conns.each { |c,s| c.close_connection }
|
187
187
|
connect!
|
188
188
|
end
|
189
189
|
|
@@ -200,19 +200,8 @@ class Bouncer
|
|
200
200
|
end
|
201
201
|
|
202
202
|
def connect!
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
if span < 5 || hosts.length < 1
|
207
|
-
EM.add_timer(5) { connect! }
|
208
|
-
return
|
209
|
-
end
|
210
|
-
@last_connect = Time.now
|
211
|
-
@cur_host = (@cur_host || 0) % hosts.length
|
212
|
-
host = hosts[@cur_host]
|
213
|
-
failsafe("connect: #{host}") do
|
214
|
-
EM.connect(host.address, host.port, IrcServerConnection, self, host.ssl)
|
215
|
-
end
|
203
|
+
@connector ||= IrcServerConnection.connector(self, network)
|
204
|
+
@connector.connect!
|
216
205
|
end
|
217
206
|
|
218
207
|
def ready!
|
@@ -97,7 +97,7 @@ module BouncerConnection
|
|
97
97
|
command = msg.command
|
98
98
|
if @state != :auth && command == 'PRIVMSG' && msg.args.first == '-tkellem'
|
99
99
|
msg_tkellem(IrcMessage.new(nil, 'TKELLEM', [msg.args.last]))
|
100
|
-
elsif command == 'TKELLEM'
|
100
|
+
elsif command == 'TKELLEM' || command == 'TK'
|
101
101
|
msg_tkellem(msg)
|
102
102
|
elsif command == 'CAP'
|
103
103
|
# TODO: full support for CAP -- this just gets mobile colloquy connecting
|
data/lib/tkellem/irc_message.rb
CHANGED
@@ -34,9 +34,11 @@ class IrcMessage < Struct.new(:prefix, :command, :args, :ctcp)
|
|
34
34
|
# /msg #someroom hey guys
|
35
35
|
def self.parse_client_command(line)
|
36
36
|
return nil unless line[0] == '/'[0]
|
37
|
+
if line =~ %r{^/msg\s+(\S+)\s+(.*)$}
|
38
|
+
line = "/PRIVMSG #{$1} :#{$2}"
|
39
|
+
end
|
37
40
|
msg = parse(line[1..-1])
|
38
41
|
return nil unless msg
|
39
|
-
msg.command = 'PRIVMSG' if msg.command == 'MSG'
|
40
42
|
msg
|
41
43
|
end
|
42
44
|
|
@@ -56,7 +58,7 @@ class IrcMessage < Struct.new(:prefix, :command, :args, :ctcp)
|
|
56
58
|
line = []
|
57
59
|
line << ":#{prefix}" unless prefix.nil?
|
58
60
|
line << command
|
59
|
-
ext_arg = args.last if args.last && args.last.match(%r{
|
61
|
+
ext_arg = args.last if args.last && args.last.match(%r{^:|\s})
|
60
62
|
line += ext_arg ? args[0...-1] : args
|
61
63
|
if ctcp?
|
62
64
|
line << ":\x01#{ctcp} #{ext_arg}\x01"
|
data/lib/tkellem/irc_server.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
require 'set'
|
2
1
|
require 'eventmachine'
|
2
|
+
require 'set'
|
3
|
+
require 'socket'
|
4
|
+
|
3
5
|
require 'tkellem/irc_message'
|
4
6
|
require 'tkellem/bouncer_connection'
|
5
7
|
|
@@ -7,45 +9,115 @@ module Tkellem
|
|
7
9
|
|
8
10
|
module IrcServerConnection
|
9
11
|
include EM::Protocols::LineText2
|
10
|
-
include Tkellem::EasyLogger
|
11
12
|
|
12
|
-
def initialize(bouncer, do_ssl)
|
13
|
+
def initialize(connection_state, bouncer, do_ssl)
|
13
14
|
set_delimiter "\r\n"
|
14
15
|
@bouncer = bouncer
|
15
16
|
@ssl = do_ssl
|
17
|
+
@connection_state = connection_state
|
18
|
+
@connected = false
|
16
19
|
end
|
17
20
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
+
def connection_completed
|
22
|
+
if @ssl
|
23
|
+
@bouncer.failsafe(:connection_completed) do
|
21
24
|
@bouncer.debug "starting TLS"
|
22
25
|
# TODO: support strict cert checks
|
23
26
|
start_tls :verify_peer => false
|
24
|
-
else
|
25
|
-
ssl_handshake_completed
|
26
27
|
end
|
28
|
+
else
|
29
|
+
ssl_handshake_completed
|
27
30
|
end
|
28
31
|
end
|
29
32
|
|
30
33
|
def ssl_handshake_completed
|
31
|
-
failsafe(:ssl_handshake_completed) do
|
32
|
-
|
34
|
+
@bouncer.failsafe(:ssl_handshake_completed) do
|
35
|
+
@connected = true
|
36
|
+
@bouncer.connection_established(self)
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
36
40
|
def receive_line(line)
|
37
|
-
failsafe(:receive_line) do
|
38
|
-
trace "from server: #{line}"
|
41
|
+
@bouncer.failsafe(:receive_line) do
|
42
|
+
@bouncer.trace "from server: #{line}"
|
39
43
|
msg = IrcMessage.parse(line)
|
40
44
|
@bouncer.server_msg(msg)
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
48
|
def unbind
|
45
|
-
failsafe(:unbind) do
|
46
|
-
@
|
49
|
+
@bouncer.failsafe(:unbind) do
|
50
|
+
if @connected
|
51
|
+
@bouncer.disconnected!
|
52
|
+
else
|
53
|
+
@bouncer.debug "Connection failed, trying next"
|
54
|
+
@connection_state.connect!
|
55
|
+
end
|
47
56
|
end
|
48
57
|
end
|
58
|
+
|
59
|
+
class ConnectionState < Struct.new(:bouncer, :network, :attempted, :getting)
|
60
|
+
def initialize(bouncer, network)
|
61
|
+
super(bouncer, network, Set.new, false)
|
62
|
+
reset
|
63
|
+
end
|
64
|
+
|
65
|
+
def connect!
|
66
|
+
raise("already in the process of getting an address") if getting
|
67
|
+
self.getting = true
|
68
|
+
network.reload
|
69
|
+
host_infos = network.hosts.map { |h| h.attributes }
|
70
|
+
EM.defer(proc { find_address(host_infos) }, method(:got_address))
|
71
|
+
end
|
72
|
+
|
73
|
+
def reset
|
74
|
+
self.attempted.clear
|
75
|
+
end
|
76
|
+
|
77
|
+
# runs in threadpool
|
78
|
+
def find_address(hosts)
|
79
|
+
candidates = Set.new
|
80
|
+
hosts.each do |host|
|
81
|
+
Socket.getaddrinfo(host['address'], host['port'], Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP).each do |found|
|
82
|
+
candidates << [found[3], host['port'], host['ssl']]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
to_try = candidates.to_a.sort_by { rand }.find { |c| !attempted.include?(c) }
|
87
|
+
if to_try.nil?
|
88
|
+
# we've tried all possible hosts, start over
|
89
|
+
return nil
|
90
|
+
end
|
91
|
+
|
92
|
+
return to_try
|
93
|
+
end
|
94
|
+
|
95
|
+
# back on event thread
|
96
|
+
def got_address(to_try)
|
97
|
+
self.getting = false
|
98
|
+
|
99
|
+
if !to_try
|
100
|
+
# sleep for a bit and try again
|
101
|
+
bouncer.debug "All available addresses failed, sleeping 5s and then trying over"
|
102
|
+
reset
|
103
|
+
EM.add_timer(5) { connect! }
|
104
|
+
return
|
105
|
+
end
|
106
|
+
|
107
|
+
attempted << to_try
|
108
|
+
address, port, ssl = to_try
|
109
|
+
|
110
|
+
bouncer.debug "Connecting to: #{Host.address_string(address, port, ssl)}"
|
111
|
+
bouncer.failsafe("connect: #{Host.address_string(address, port, ssl)}") do
|
112
|
+
EM.connect(address, port, IrcServerConnection, self, bouncer, ssl)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.connector(bouncer, network)
|
118
|
+
ConnectionState.new(bouncer, network)
|
119
|
+
end
|
120
|
+
|
49
121
|
end
|
50
122
|
|
51
123
|
end
|
data/lib/tkellem/models/host.rb
CHANGED
data/lib/tkellem/version.rb
CHANGED
data/spec/irc_message_spec.rb
CHANGED
@@ -22,6 +22,14 @@ describe IrcMessage, ".parse" do
|
|
22
22
|
line.replay.should == orig
|
23
23
|
end
|
24
24
|
|
25
|
+
it "should parse and replay messages with leading colons" do
|
26
|
+
orig = "MSG #myroom ::)"
|
27
|
+
line = IrcMessage.parse(orig)
|
28
|
+
line.command.should == "MSG"
|
29
|
+
line.args.should == ["#myroom", ":)"]
|
30
|
+
line.replay.should == orig
|
31
|
+
end
|
32
|
+
|
25
33
|
it "should parse with no arguments" do
|
26
34
|
line = IrcMessage.parse("AWAY")
|
27
35
|
line.command.should == "AWAY"
|
data/spec/irc_server_spec.rb
CHANGED
@@ -9,7 +9,8 @@ describe Bouncer, "connection" do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def make_server
|
12
|
-
|
12
|
+
network = Network.create!(:hosts => [Host.create!(:address => 'localhost', :port => 4321)], :name => 'test')
|
13
|
+
b = Bouncer.new(NetworkUser.create!(:user => User.new(:username => 'speccer'), :network => network))
|
13
14
|
b
|
14
15
|
end
|
15
16
|
|
@@ -66,7 +67,7 @@ describe Bouncer, "connection" do
|
|
66
67
|
network_user
|
67
68
|
@bouncer = $tk_server.bouncers.values.last
|
68
69
|
if opts[:connect]
|
69
|
-
@server_conn = em(IrcServerConnection).new(@bouncer, false)
|
70
|
+
@server_conn = em(IrcServerConnection).new(nil, @bouncer, false)
|
70
71
|
@server_conn.stub!(:send_data)
|
71
72
|
@bouncer.connection_established(@server_conn)
|
72
73
|
@bouncer.send :ready!
|
@@ -96,7 +97,7 @@ describe Bouncer, "connection" do
|
|
96
97
|
it "should attempt another nick if the default is taken" do
|
97
98
|
network_user(:nick => 'mynick')
|
98
99
|
bouncer
|
99
|
-
@server_conn = em(IrcServerConnection).new(@bouncer, false)
|
100
|
+
@server_conn = em(IrcServerConnection).new(nil, @bouncer, false)
|
100
101
|
@server_conn.stub!(:send_data)
|
101
102
|
@bouncer.connection_established(@server_conn)
|
102
103
|
@server_conn.should_receive(:send_data).with("NICK mynick_\r\n")
|
data/tkellem.gemspec
CHANGED
@@ -18,13 +18,9 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
20
|
s.add_dependency "eventmachine", "~> 0.12.10"
|
21
|
-
s.add_dependency "daemons", "~> 1.1.0"
|
22
|
-
s.add_dependency "json"
|
23
21
|
s.add_dependency "activerecord", "~> 3.0.0"
|
24
22
|
s.add_dependency "sqlite3", "~> 1.3.3"
|
25
23
|
|
26
24
|
s.add_development_dependency "rspec", "~> 2.5"
|
27
25
|
s.add_development_dependency "rcov"
|
28
|
-
s.add_development_dependency "yard"
|
29
26
|
end
|
30
|
-
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: tkellem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.8.
|
5
|
+
version: 0.8.8
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Brian Palmer
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-07-01 00:00:00 -06:00
|
14
14
|
default_executable: tkellem
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -24,83 +24,50 @@ dependencies:
|
|
24
24
|
version: 0.12.10
|
25
25
|
type: :runtime
|
26
26
|
version_requirements: *id001
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: daemons
|
29
|
-
prerelease: false
|
30
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
-
none: false
|
32
|
-
requirements:
|
33
|
-
- - ~>
|
34
|
-
- !ruby/object:Gem::Version
|
35
|
-
version: 1.1.0
|
36
|
-
type: :runtime
|
37
|
-
version_requirements: *id002
|
38
|
-
- !ruby/object:Gem::Dependency
|
39
|
-
name: json
|
40
|
-
prerelease: false
|
41
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
|
-
requirements:
|
44
|
-
- - ">="
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: "0"
|
47
|
-
type: :runtime
|
48
|
-
version_requirements: *id003
|
49
27
|
- !ruby/object:Gem::Dependency
|
50
28
|
name: activerecord
|
51
29
|
prerelease: false
|
52
|
-
requirement: &
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
53
31
|
none: false
|
54
32
|
requirements:
|
55
33
|
- - ~>
|
56
34
|
- !ruby/object:Gem::Version
|
57
35
|
version: 3.0.0
|
58
36
|
type: :runtime
|
59
|
-
version_requirements: *
|
37
|
+
version_requirements: *id002
|
60
38
|
- !ruby/object:Gem::Dependency
|
61
39
|
name: sqlite3
|
62
40
|
prerelease: false
|
63
|
-
requirement: &
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
64
42
|
none: false
|
65
43
|
requirements:
|
66
44
|
- - ~>
|
67
45
|
- !ruby/object:Gem::Version
|
68
46
|
version: 1.3.3
|
69
47
|
type: :runtime
|
70
|
-
version_requirements: *
|
48
|
+
version_requirements: *id003
|
71
49
|
- !ruby/object:Gem::Dependency
|
72
50
|
name: rspec
|
73
51
|
prerelease: false
|
74
|
-
requirement: &
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
75
53
|
none: false
|
76
54
|
requirements:
|
77
55
|
- - ~>
|
78
56
|
- !ruby/object:Gem::Version
|
79
57
|
version: "2.5"
|
80
58
|
type: :development
|
81
|
-
version_requirements: *
|
59
|
+
version_requirements: *id004
|
82
60
|
- !ruby/object:Gem::Dependency
|
83
61
|
name: rcov
|
84
62
|
prerelease: false
|
85
|
-
requirement: &
|
86
|
-
none: false
|
87
|
-
requirements:
|
88
|
-
- - ">="
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: "0"
|
91
|
-
type: :development
|
92
|
-
version_requirements: *id007
|
93
|
-
- !ruby/object:Gem::Dependency
|
94
|
-
name: yard
|
95
|
-
prerelease: false
|
96
|
-
requirement: &id008 !ruby/object:Gem::Requirement
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
97
64
|
none: false
|
98
65
|
requirements:
|
99
66
|
- - ">="
|
100
67
|
- !ruby/object:Gem::Version
|
101
68
|
version: "0"
|
102
69
|
type: :development
|
103
|
-
version_requirements: *
|
70
|
+
version_requirements: *id005
|
104
71
|
description:
|
105
72
|
email:
|
106
73
|
- brian@codekitchen.net
|