tkellem 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Brian Palmer <brian@codekitchen.net>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
data/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # tkellem
2
+
3
+ tkellem is an IRC bouncer, a proxy that keeps you permanently logged on to an
4
+ IRC server and stores all messages so that when your client next connects, you
5
+ can see the backlog of what happened while you were gone.
6
+
7
+ tkellem supports multiple device-independent backlogs, and connecting to
8
+ multiple IRC servers all from the same process.
9
+
10
+ ## IMPORTANT
11
+
12
+ This is a very, very early version. Only the basic functionality is implemented.
13
+ You probably don't want this yet.
14
+
15
+ ## Usage
16
+
17
+ bin/tkellem examples/config.yml
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rspec'
4
+ require 'rspec/core/rake_task'
5
+
6
+ task :default => :spec
7
+
8
+ RSpec::Core::RakeTask.new(:spec) do |spec|
9
+ spec.pattern = 'spec/spec_helper.rb'
10
+ end
11
+
12
+ begin
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gem|
15
+ gem.name = 'tkellem'
16
+ gem.summary = 'IRC bouncer with multi-client support'
17
+ gem.email = 'brian@codekitchen.net'
18
+ gem.homepage = 'http://github.com/codekitchen/tkellem'
19
+ gem.authors = ['Brian Palmer']
20
+ gem.add_dependency 'eventmachine'
21
+ gem.executables = %w(tkellem)
22
+ end
23
+
24
+ Jeweler::GemcutterTasks.new
25
+ rescue LoadError
26
+ # om nom nom
27
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7.0
data/bin/tkellem ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+ require 'yaml'
8
+ require 'optparse'
9
+
10
+ opts = OptionParser.new
11
+ opts.banner = <<BANNER
12
+ Usage: #{opts.program_name} PATH_TO_CONFIG
13
+
14
+ Start a tkellem instance using the specified configuration file (or ~/.tkellem/config.yml if none given)
15
+ BANNER
16
+
17
+ opts.on_tail("-h", "--help") { puts opts; exit }
18
+
19
+ rest = opts.parse(ARGV)
20
+ config_filename = rest.first || File.expand_path(ENV["HOME"]+'/.tkellem/config.yml')
21
+
22
+ config = YAML.load_file(config_filename)
23
+
24
+ $LOAD_PATH.push(File.expand_path(File.dirname(__FILE__)+'/../lib'))
25
+ require 'tkellem'
26
+
27
+ EM.run do
28
+ bouncer =
29
+ Tkellem::Bouncer.new(config['listen'] || '0.0.0.0',
30
+ config['port'] || 10001,
31
+ config['ssl'])
32
+
33
+ bouncer.max_backlog = config['max_backlog'].to_i
34
+
35
+ bouncer.on_authenticate do |username, password, irc_server|
36
+ server_config = config['connections'][irc_server.name]
37
+ if server_config && password_sha1 = server_config['password_sha1']
38
+ require 'openssl'
39
+ password_sha1 == OpenSSL::Digest::SHA1.hexdigest(password)
40
+ else
41
+ true
42
+ end
43
+ end
44
+
45
+ def add_irc_server(bouncer, name, conn)
46
+ server = bouncer.add_irc_server(name,
47
+ conn['host'], conn['port'], conn['ssl'],
48
+ conn['nick'])
49
+
50
+ (conn['rooms'] || []).each { |room| server.join_room(room['name']) }
51
+ conn['clients'].each { |client| server.add_client(client['name']) }
52
+ end
53
+
54
+ config['connections'].each do |name, conn|
55
+ Tkellem::EasyLogger.logger.info("adding new connection #{name}")
56
+ add_irc_server(bouncer, name, conn)
57
+ end
58
+
59
+ Signal.trap('HUP') do
60
+ Tkellem::EasyLogger.logger.warn("got HUP, reloading #{config_filename}")
61
+ new_config = YAML.load_file(config_filename)
62
+
63
+ bouncer.max_backlog = new_config['max_backlog'].to_i
64
+
65
+ # find changed connections
66
+ to_delete = config['connections'].keys
67
+
68
+ new_config['connections'].each do |name, new_conn|
69
+ to_delete.delete(name)
70
+ conn = config['connections'][name]
71
+ if !conn
72
+ Tkellem::EasyLogger.logger.info("adding new connection #{name}")
73
+ add_irc_server(bouncer, name, new_conn)
74
+ else
75
+ if new_conn['host'] != conn['host'] || new_conn['port'] != conn['port'] || new_conn['ssl'] != conn['ssl']
76
+ Tkellem::EasyLogger.logger.info("server settings changed for #{name}, dropping clients and reconnecting")
77
+ bouncer.remove_irc_server(name)
78
+ add_irc_server(bouncer, name, new_conn)
79
+ elsif conn['clients'] != new_conn['clients']
80
+ irc_server = bouncer.get_irc_server(name)
81
+
82
+ # we don't have to reconnect, but maybe clients changed. we ignore
83
+ # changes to nick and rooms on HUP, since those are dynamic once tkellem
84
+ # connects. password change will be caught on the next client
85
+ # connect.
86
+ clients_to_delete = conn['clients'].map { |c| c['name'] }
87
+ new_conn['clients'].each do |new_client|
88
+ clients_to_delete.delete(new_client['name'])
89
+ # add -- if already exists, this is a no-op. this may change in the
90
+ # future though...
91
+ Tkellem::EasyLogger.logger.info("adding client #{new_client['name']} to server #{name}")
92
+ irc_server.add_client(new_client['name'])
93
+ end
94
+ clients_to_delete.each do |client_name|
95
+ Tkellem::EasyLogger.logger.info("removing client #{client_name} from server #{name}")
96
+ irc_server.remove_client(client_name) if irc_server
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ to_delete.each do |name|
103
+ Tkellem::EasyLogger.logger.info("deleting connection #{name}")
104
+ bouncer.remove_irc_server(name)
105
+ end
106
+
107
+ config = new_config
108
+ end
109
+
110
+ end
@@ -0,0 +1,29 @@
1
+ # $ bin/tkellem examples/config.yml
2
+ #
3
+ # to connect: connect to localhost port 10001, ssl enabled, "real name" set to
4
+ #
5
+ # <connection_name> <client_name>
6
+ #
7
+ # e.g.:
8
+ #
9
+ # freenode laptop
10
+ ---
11
+ listen: 0.0.0.0
12
+ port: 10001
13
+ ssl: true
14
+ # max_backlog: 500
15
+ connections:
16
+ freenode:
17
+ host: irc.freenode.org
18
+ port: 6667
19
+ ssl: false
20
+ nick: tkellem_r0ck
21
+ # Uncomment to enable password auth (there's no auth by default, which
22
+ # means anybody can connect). You can generate a password_sha1 like so:
23
+ # echo -n 'tkellem_r0ck' | openssl sha1
24
+ # password_sha1: a4f4a3b97c7b8a028d4e3f3fee85d6e5626baba5
25
+ rooms:
26
+ - name: "#tkellem_test"
27
+ clients:
28
+ - name: laptop
29
+ - name: iphone
@@ -0,0 +1,85 @@
1
+ require 'tkellem/irc_line'
2
+
3
+ module Tkellem
4
+
5
+ # Normally there will be one client per backlog, but there can be more than one
6
+ # connection for the same backlog, if two or more IRC clients connect with the
7
+ # same client name. That situation is equivalent to how most multi-connection
8
+ # bouncers like bip work.
9
+
10
+ class Backlog
11
+
12
+ class BacklogLine < Struct.new(:irc_line, :time)
13
+ end
14
+
15
+ def initialize(name, max_backlog = nil)
16
+ @name = name
17
+ @backlog = []
18
+ @pm_backlogs = Hash.new { |h,k| h[k] = [] }
19
+ @active_conns = []
20
+ @max_backlog = max_backlog
21
+ end
22
+ attr_reader :name, :backlog, :active_conns, :pm_backlogs, :max_backlog
23
+
24
+ def handle_message(msg)
25
+ # TODO: only send back response messages like WHO, NAMES, etc. to the
26
+ # BouncerConnection that requested it.
27
+ if !active_conns.empty?
28
+ case msg.command
29
+ when /3\d\d/, /join/i, /part/i
30
+ # transient response -- we want to forward these, but not backlog
31
+ active_conns.each { |conn| conn.transient_response(msg) }
32
+ when /privmsg/i
33
+ active_conns.each { |conn| conn.send_msg(msg) }
34
+ else
35
+ # do nothing?
36
+ end
37
+ elsif msg.command.match(/privmsg/i)
38
+ if msg.args.first.match(/^#/)
39
+ # room privmsg always goes in a specific backlog
40
+ pm_target = msg.args.first
41
+ bl = pm_backlogs[pm_target]
42
+ else
43
+ # other messages go in the general backlog
44
+ bl = backlog
45
+ end
46
+ bl.push(BacklogLine.new(msg, Time.now))
47
+ limit_backlog(bl)
48
+ end
49
+ end
50
+
51
+ def limit_backlog(bl)
52
+ bl.shift until !max_backlog || bl.size <= max_backlog
53
+ end
54
+
55
+ def max_backlog=(new_val)
56
+ @max_backlog = new_val
57
+ limit_backlog(backlog)
58
+ pm_backlogs.each { |k,bl| limit_backlog(bl) }
59
+ end
60
+
61
+ def add_conn(bouncer_conn)
62
+ active_conns << bouncer_conn
63
+ end
64
+
65
+ def remove_conn(bouncer_conn)
66
+ active_conns.delete(bouncer_conn)
67
+ end
68
+
69
+ def send_backlog(conn, pm_target = nil)
70
+ if pm_target
71
+ # send room-specific backlog
72
+ msgs = pm_backlogs.key?(pm_target) ? pm_backlogs[pm_target] : []
73
+ else
74
+ # send the general backlog
75
+ msgs = backlog
76
+ end
77
+
78
+ until msgs.empty?
79
+ backlog_line = msgs.shift
80
+ conn.send_msg(backlog_line.irc_line.with_timestamp(backlog_line.time))
81
+ end
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,52 @@
1
+ require 'eventmachine'
2
+
3
+ require 'tkellem/irc_server'
4
+
5
+ module Tkellem
6
+ class Bouncer
7
+ def initialize(listen_address, port, do_ssl)
8
+ @irc_servers = {}
9
+ @max_backlog = nil
10
+ @server = EM.start_server(listen_address, port, BouncerConnection,
11
+ self, do_ssl)
12
+ end
13
+
14
+ def add_irc_server(name, host, port, do_ssl, nick)
15
+ server = EM.connect(host, port, IrcServer, self, name, do_ssl, nick)
16
+ @irc_servers[name] = server
17
+ server.set_max_backlog(@max_backlog) if @max_backlog
18
+ server
19
+ end
20
+
21
+ def remove_irc_server(name)
22
+ server = @irc_servers.delete(name)
23
+ if server
24
+ server.close_connection(true)
25
+ end
26
+ end
27
+
28
+ def on_authenticate(&block)
29
+ @auth_block = block
30
+ end
31
+
32
+ def max_backlog=(max_backlog)
33
+ @max_backlog = max_backlog && max_backlog > 0 ? max_backlog : nil
34
+ @irc_servers.each { |name, server| server.set_max_backlog(@max_backlog) }
35
+ end
36
+
37
+
38
+ # Internal API
39
+
40
+ def get_irc_server(name) #:nodoc:
41
+ @irc_servers[name]
42
+ end
43
+
44
+ def do_auth(username, password, irc_server) #:nodoc:
45
+ if @auth_block
46
+ @auth_block.call(username, password.to_s, irc_server)
47
+ else
48
+ true
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,149 @@
1
+ require 'eventmachine'
2
+ require 'tkellem/irc_line'
3
+ require 'tkellem/backlog'
4
+
5
+ module Tkellem
6
+
7
+ module BouncerConnection
8
+ include EM::Protocols::LineText2
9
+ include Tkellem::EasyLogger
10
+
11
+ def initialize(bouncer, do_ssl)
12
+ set_delimiter "\r\n"
13
+
14
+ @ssl = do_ssl
15
+ @bouncer = bouncer
16
+
17
+ @irc_server = nil
18
+ @backlog = nil
19
+ @nick = nil
20
+ @conn_name = nil
21
+ @name = nil
22
+ end
23
+ attr_reader :ssl, :irc_server, :backlog, :bouncer, :nick
24
+
25
+ def connected?
26
+ !!irc_server
27
+ end
28
+
29
+ def name
30
+ @name || "new-conn"
31
+ end
32
+
33
+ def post_init
34
+ if ssl
35
+ debug "starting TLS"
36
+ start_tls :verify_peer => false
37
+ end
38
+ end
39
+
40
+ def ssl_handshake_completed
41
+ debug "TLS complete"
42
+ end
43
+
44
+ def error!(msg)
45
+ info("ERROR :#{msg}")
46
+ send_msg("ERROR :#{msg}")
47
+ close_connection(true)
48
+ end
49
+
50
+ def connect(conn_name, client_name, password)
51
+ @irc_server = bouncer.get_irc_server(conn_name.downcase)
52
+ unless irc_server && irc_server.connected?
53
+ error!("unknown connection #{conn_name}")
54
+ return
55
+ end
56
+
57
+ unless bouncer.do_auth(conn_name, @password, irc_server)
58
+ error!("bad auth, please check your password")
59
+ @irc_server = @conn_name = @name = @backlog = nil
60
+ return
61
+ end
62
+
63
+ @conn_name = conn_name
64
+ @name = client_name
65
+ @backlog = irc_server.bouncer_connect(self)
66
+ unless backlog
67
+ error!("unknown client #{client_name}")
68
+ @irc_server = @conn_name = @name = nil
69
+ return
70
+ end
71
+
72
+ info "connected"
73
+
74
+ irc_server.send_welcome(self)
75
+ backlog.send_backlog(self)
76
+ irc_server.rooms.each { |room| simulate_join(room) }
77
+ end
78
+
79
+ def tkellem(msg)
80
+ case msg.args.first
81
+ when /nothing_yet/i
82
+ else
83
+ send_msg(":tkellem!tkellem@tkellem PRIVMSG #{nick} :Unknown tkellem command #{msg.args.first}")
84
+ end
85
+ end
86
+
87
+ def receive_line(line)
88
+ trace "from client: #{line}"
89
+ msg = IrcLine.parse(line)
90
+ case msg.command
91
+ when /tkellem/i
92
+ tkellem(msg)
93
+ when /pass/i
94
+ @password = msg.args.first
95
+ when /user/i
96
+ conn_name, client_name = msg.args.last.strip.split(' ')
97
+ connect(conn_name, client_name, @password)
98
+ when /nick/i
99
+ if connected?
100
+ irc_server.change_nick(msg.last)
101
+ else
102
+ @nick = msg.last
103
+ end
104
+ when /quit/i
105
+ # DENIED
106
+ close_connection
107
+ when /ping/i
108
+ send_msg(":tkellem PONG tkellem :#{msg.last}")
109
+ else
110
+ if !connected?
111
+ close_connection
112
+ else
113
+ # pay it forward
114
+ irc_server.send_msg(msg)
115
+ end
116
+ end
117
+ end
118
+
119
+ def simulate_join(room)
120
+ send_msg(":#{irc_server.nick}!#{name}@tkellem JOIN #{room}")
121
+ # TODO: intercept the NAMES response so that only this bouncer gets it
122
+ # Otherwise other clients might show an "in this room" line.
123
+ irc_server.send_msg("NAMES #{room}\r\n")
124
+ end
125
+
126
+ def transient_response(msg)
127
+ send_msg(msg)
128
+ if msg.command == "366"
129
+ # finished joining this room, let's backlog it
130
+ debug "got final NAMES for #{msg.args[1]}, sending backlog"
131
+ backlog.send_backlog(self, msg.args[1])
132
+ end
133
+ end
134
+
135
+ def send_msg(msg)
136
+ trace "to client: #{msg}"
137
+ send_data("#{msg}\r\n")
138
+ end
139
+
140
+ def log_name
141
+ "#{@conn_name}-#{name}"
142
+ end
143
+
144
+ def unbind
145
+ irc_server.bouncer_disconnect(self) if connected?
146
+ end
147
+ end
148
+
149
+ end
@@ -0,0 +1,58 @@
1
+ module Tkellem
2
+
3
+ class IrcLine
4
+ RE = %r{(:[^ ]+ )?([^ ]*)(.*)}i
5
+
6
+ def self.parse(line)
7
+ md = RE.match(line) or raise("invalid input: #{line.inspect}")
8
+
9
+ self.new(line, md[1], md[2], md[3])
10
+ end
11
+
12
+ attr_reader :prefix, :command, :args
13
+
14
+ def initialize(orig, prefix, command, args)
15
+ @orig = orig
16
+ @prefix = prefix ? prefix.strip : nil
17
+ @command = command
18
+
19
+ args.strip!
20
+ idx = args.index(":")
21
+ if idx
22
+ @args = args[0...idx].split(' ') + [args[idx+1..-1]]
23
+ else
24
+ @args = args.split(' ')
25
+ end
26
+ end
27
+
28
+ def command?(cmd)
29
+ @command.downcase == cmd.downcase
30
+ end
31
+
32
+ def replay
33
+ @orig
34
+ end
35
+ alias_method :to_s, :replay
36
+
37
+ def last
38
+ args.last
39
+ end
40
+
41
+ def target_user
42
+ if prefix && md = %r{^:([^!]+)}.match(prefix)
43
+ md[1]
44
+ else
45
+ nil
46
+ end
47
+ end
48
+
49
+ def with_timestamp(timestamp)
50
+ new_command = [prefix, command]
51
+ new_command += args[0..-2]
52
+ new_command.push("#{timestamp.strftime("%H:%M:%S")}> #{args.last}")
53
+ new_command.join(' ')
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,156 @@
1
+ require 'set'
2
+ require 'eventmachine'
3
+ require 'tkellem/irc_line'
4
+ require 'tkellem/bouncer_connection'
5
+ require 'tkellem/backlog'
6
+
7
+ module Tkellem
8
+
9
+ module IrcServer
10
+ include EM::Protocols::LineText2
11
+ include Tkellem::EasyLogger
12
+
13
+ def initialize(bouncer, name, do_ssl, nick)
14
+ set_delimiter "\r\n"
15
+
16
+ @bouncer = bouncer
17
+ @name = name
18
+ @ssl = do_ssl
19
+ @nick = nick
20
+
21
+ @max_backlog = nil
22
+ @connected = false
23
+ @welcomes = []
24
+ @rooms = Set.new
25
+ @backlogs = {}
26
+ @active_conns = []
27
+ @joined_rooms = false
28
+ @pending_rooms = []
29
+ end
30
+ attr_reader :name, :backlogs, :welcomes, :rooms, :nick, :active_conns
31
+ alias_method :log_name, :name
32
+
33
+ def connected?
34
+ @connected
35
+ end
36
+
37
+ def set_max_backlog(max_backlog)
38
+ @max_backlog = max_backlog
39
+ backlogs.each { |name, backlog| backlog.max_backlog = max_backlog }
40
+ end
41
+
42
+ def post_init
43
+ if @ssl
44
+ debug "starting TLS"
45
+ # TODO: support strict cert checks
46
+ start_tls :verify_peer => false
47
+ else
48
+ ssl_handshake_completed
49
+ end
50
+ end
51
+
52
+ def ssl_handshake_completed
53
+ # TODO: support sending a real username, realname, etc
54
+ send_msg("USER #{nick} localhost blah :#{nick}")
55
+ change_nick(nick, true)
56
+ end
57
+
58
+ def receive_line(line)
59
+ trace "from server: #{line}"
60
+ msg = IrcLine.parse(line)
61
+
62
+ case msg.command
63
+ when /0\d\d/, /2[56]\d/, /37[256]/
64
+ welcomes << msg
65
+ got_welcome if msg.command == "376" # end of MOTD
66
+ when /join/i
67
+ debug "#{msg.target_user} joined #{msg.last}"
68
+ rooms << msg.last if msg.target_user == nick
69
+ when /part/i
70
+ debug "#{msg.target_user} left #{msg.last}"
71
+ rooms.delete(msg.last) if msg.target_user == nick
72
+ when /ping/i
73
+ send_msg("PONG #{nick}!tkellem #{msg.args.first}")
74
+ when /pong/i
75
+ # swallow it, we handle ping-pong from clients separately, in
76
+ # BouncerConnection
77
+ else
78
+ end
79
+
80
+ backlogs.each { |name, backlog| backlog.handle_message(msg) }
81
+ end
82
+
83
+ def got_welcome
84
+ return if @joined_rooms
85
+ @joined_rooms = true
86
+ @pending_rooms.each do |room|
87
+ join_room(room)
88
+ end
89
+ @pending_rooms.clear
90
+
91
+ # We're all initialized, allow connections
92
+ @connected = true
93
+ end
94
+
95
+ def change_nick(new_nick, force = false)
96
+ return if !force && new_nick == @nick
97
+ @nick = new_nick
98
+ send_msg("NICK #{new_nick}")
99
+ end
100
+
101
+ def join_room(room_name)
102
+ if @joined_rooms
103
+ send_msg("JOIN #{room_name}")
104
+ else
105
+ @pending_rooms << room_name
106
+ end
107
+ end
108
+
109
+ def add_client(name)
110
+ return if backlogs[name]
111
+ backlog = Backlog.new(name, @max_backlog)
112
+ backlogs[name] = backlog
113
+ end
114
+
115
+ def remove_client(name)
116
+ backlog = backlogs.delete(name)
117
+ if backlog
118
+ backlog.active_conns.each do |conn|
119
+ conn.error!("client removed")
120
+ end
121
+ end
122
+ end
123
+
124
+ def send_msg(msg)
125
+ trace "to server: #{msg}"
126
+ send_data("#{msg}\r\n")
127
+ end
128
+
129
+ def send_welcome(bouncer_conn)
130
+ welcomes.each { |msg| bouncer_conn.send_msg(msg) }
131
+ end
132
+
133
+ def unbind
134
+ debug "OMG we got disconnected."
135
+ # TODO: reconnect if desired. but not if this server was explicitly shut
136
+ # down or removed.
137
+ backlogs.keys.each { |name| remove_client(name) }
138
+ end
139
+
140
+ def bouncer_connect(bouncer_conn)
141
+ return nil unless backlogs[bouncer_conn.name]
142
+
143
+ active_conns << bouncer_conn
144
+ backlogs[bouncer_conn.name].add_conn(bouncer_conn)
145
+ backlogs[bouncer_conn.name]
146
+ end
147
+
148
+ def bouncer_disconnect(bouncer_conn)
149
+ return nil unless backlogs[bouncer_conn.name]
150
+
151
+ backlogs[bouncer_conn.name].remove_conn(bouncer_conn)
152
+ active_conns.delete(bouncer_conn)
153
+ end
154
+ end
155
+
156
+ end
data/lib/tkellem.rb ADDED
@@ -0,0 +1,45 @@
1
+ begin
2
+ require 'rubygems'
3
+ rescue LoadError
4
+ end
5
+
6
+ pathname = File.expand_path(File.dirname(__FILE__))
7
+ $LOAD_PATH.push(pathname) unless $LOAD_PATH.include?(pathname)
8
+
9
+ module Tkellem
10
+ module EasyLogger
11
+ require 'logger'
12
+
13
+ def self.logger=(new_logger)
14
+ @logger = new_logger
15
+ end
16
+ def self.logger
17
+ return @logger if @logger
18
+ @logger = Logger.new(STDERR)
19
+ @logger.datetime_format = "%Y-%m-%d"
20
+ @logger
21
+ end
22
+
23
+ def self.trace=(val)
24
+ @trace = val
25
+ end
26
+ def self.trace
27
+ @trace || @trace = false
28
+ end
29
+
30
+ def trace(msg)
31
+ puts("TRACE: #{log_name}: #{msg}") if EasyLogger.trace
32
+ end
33
+
34
+ ::Logger::Severity.constants.each do |level|
35
+ next if level == "UNKNOWN"
36
+ module_eval(<<-EVAL, __FILE__, __LINE__)
37
+ def #{level.downcase}(msg)
38
+ EasyLogger.logger.#{level.downcase}("\#{log_name} (\#{object_id}): \#{msg}")
39
+ end
40
+ EVAL
41
+ end
42
+ end
43
+ end
44
+
45
+ require 'tkellem/bouncer'
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'tkellem'
5
+ require 'rspec'
6
+
7
+ RSpec.configure do |config|
8
+ end
9
+
10
+ puts "Yeah... not so much right now"
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tkellem
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 7
8
+ - 0
9
+ version: 0.7.0
10
+ platform: ruby
11
+ authors:
12
+ - Brian Palmer
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-17 00:00:00 -07:00
18
+ default_executable: tkellem
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: eventmachine
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description:
34
+ email: brian@codekitchen.net
35
+ executables:
36
+ - tkellem
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - LICENSE
41
+ - README.md
42
+ files:
43
+ - LICENSE
44
+ - README.md
45
+ - Rakefile
46
+ - VERSION
47
+ - bin/tkellem
48
+ - examples/config.yml
49
+ - lib/tkellem.rb
50
+ - lib/tkellem/backlog.rb
51
+ - lib/tkellem/bouncer.rb
52
+ - lib/tkellem/bouncer_connection.rb
53
+ - lib/tkellem/irc_line.rb
54
+ - lib/tkellem/irc_server.rb
55
+ - spec/spec_helper.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/codekitchen/tkellem
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options: []
62
+
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.3.7
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: IRC bouncer with multi-client support
88
+ test_files:
89
+ - spec/spec_helper.rb