tkellem 0.7.0

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