hector 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/hector.rb +42 -0
- data/lib/hector/boot.rb +62 -0
- data/lib/hector/channel.rb +106 -0
- data/lib/hector/commands/away.rb +16 -0
- data/lib/hector/commands/join.rb +16 -0
- data/lib/hector/commands/mode.rb +31 -0
- data/lib/hector/commands/names.rb +16 -0
- data/lib/hector/commands/nick.rb +11 -0
- data/lib/hector/commands/notice.rb +10 -0
- data/lib/hector/commands/part.rb +11 -0
- data/lib/hector/commands/ping.rb +9 -0
- data/lib/hector/commands/pong.rb +9 -0
- data/lib/hector/commands/privmsg.rb +16 -0
- data/lib/hector/commands/quit.rb +10 -0
- data/lib/hector/commands/realname.rb +10 -0
- data/lib/hector/commands/topic.rb +26 -0
- data/lib/hector/commands/who.rb +29 -0
- data/lib/hector/commands/whois.rb +28 -0
- data/lib/hector/concerns/authentication.rb +45 -0
- data/lib/hector/concerns/keep_alive.rb +24 -0
- data/lib/hector/concerns/presence.rb +59 -0
- data/lib/hector/connection.rb +85 -0
- data/lib/hector/deference.rb +11 -0
- data/lib/hector/errors.rb +30 -0
- data/lib/hector/heartbeat.rb +25 -0
- data/lib/hector/identity.rb +23 -0
- data/lib/hector/logging.rb +16 -0
- data/lib/hector/request.rb +42 -0
- data/lib/hector/response.rb +48 -0
- data/lib/hector/server.rb +14 -0
- data/lib/hector/service.rb +30 -0
- data/lib/hector/session.rb +185 -0
- data/lib/hector/user_session.rb +38 -0
- data/lib/hector/version.rb +3 -0
- data/lib/hector/yaml_identity_adapter.rb +56 -0
- metadata +133 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
module Hector
|
2
|
+
class << self
|
3
|
+
attr_accessor :server_name
|
4
|
+
|
5
|
+
def start_server(address = "0.0.0.0", port = 6767, ssl_port = 6868)
|
6
|
+
EventMachine.start_server(address, port, Connection)
|
7
|
+
EventMachine.start_server(address, ssl_port, SSLConnection)
|
8
|
+
logger.info("Hector running on #{address}:#{port}")
|
9
|
+
logger.info("Secure Hector running on #{address}:#{ssl_port}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
self.server_name = "hector.irc"
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Hector
|
2
|
+
class Service < Session
|
3
|
+
protected
|
4
|
+
def deliver_message_from_origin(text)
|
5
|
+
deliver_message_from_session(origin, text)
|
6
|
+
end
|
7
|
+
|
8
|
+
def deliver_message_from_service(text)
|
9
|
+
deliver_message_from_session(self, text)
|
10
|
+
end
|
11
|
+
|
12
|
+
def deliver_message_from_session(session, text)
|
13
|
+
command, destination = response.command, find(response.args.first)
|
14
|
+
Hector.defer do
|
15
|
+
destination.deliver(command, session, :source => session.source, :text => text)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def intercept(pattern)
|
20
|
+
if response.text =~ pattern
|
21
|
+
yield *$~
|
22
|
+
throw :stop
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def origin
|
27
|
+
find(response.source[/^([^!]+)!/, 1])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module Hector
|
2
|
+
class Session
|
3
|
+
include Concerns::KeepAlive
|
4
|
+
include Concerns::Presence
|
5
|
+
|
6
|
+
include Commands::Join
|
7
|
+
include Commands::Mode
|
8
|
+
include Commands::Names
|
9
|
+
include Commands::Nick
|
10
|
+
include Commands::Notice
|
11
|
+
include Commands::Part
|
12
|
+
include Commands::Ping
|
13
|
+
include Commands::Pong
|
14
|
+
include Commands::Privmsg
|
15
|
+
include Commands::Quit
|
16
|
+
include Commands::Realname
|
17
|
+
include Commands::Topic
|
18
|
+
include Commands::Who
|
19
|
+
include Commands::Whois
|
20
|
+
include Commands::Away
|
21
|
+
|
22
|
+
attr_reader :nickname, :request, :response, :away_message
|
23
|
+
|
24
|
+
SESSIONS = {}
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def all
|
28
|
+
sessions.values.grep(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def nicknames
|
32
|
+
sessions.keys
|
33
|
+
end
|
34
|
+
|
35
|
+
def find(nickname)
|
36
|
+
sessions[normalize(nickname)]
|
37
|
+
end
|
38
|
+
|
39
|
+
def rename(from, to)
|
40
|
+
if find(to)
|
41
|
+
raise NicknameInUse, to
|
42
|
+
else
|
43
|
+
find(from).tap do |session|
|
44
|
+
delete(from)
|
45
|
+
sessions[normalize(to)] = session
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete(nickname)
|
51
|
+
sessions.delete(normalize(nickname))
|
52
|
+
end
|
53
|
+
|
54
|
+
def broadcast_to(sessions, command, *args)
|
55
|
+
except = args.last.delete(:except) if args.last.is_a?(Hash)
|
56
|
+
sessions.each do |session|
|
57
|
+
session.respond_with(command, *args) unless session == except
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def normalize(nickname)
|
62
|
+
if nickname =~ /^\w[\w-]{0,15}$/u
|
63
|
+
nickname.downcase
|
64
|
+
else
|
65
|
+
raise ErroneousNickname, nickname
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def register(session)
|
70
|
+
sessions[normalize(session.nickname)] = session
|
71
|
+
session
|
72
|
+
end
|
73
|
+
|
74
|
+
def reset!
|
75
|
+
sessions.clear
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
def sessions
|
80
|
+
SESSIONS
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize(nickname)
|
85
|
+
@nickname = nickname
|
86
|
+
end
|
87
|
+
|
88
|
+
def broadcast(command, *args)
|
89
|
+
Session.broadcast_to(peer_sessions, command, *args)
|
90
|
+
end
|
91
|
+
|
92
|
+
def channel?
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
96
|
+
def deliver(message_type, session, options)
|
97
|
+
respond_with(message_type, nickname, options)
|
98
|
+
end
|
99
|
+
|
100
|
+
def destroy
|
101
|
+
self.class.delete(nickname)
|
102
|
+
end
|
103
|
+
|
104
|
+
def find(name)
|
105
|
+
destination_klass_for(name).find(name).tap do |destination|
|
106
|
+
raise NoSuchNickOrChannel, name unless destination
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def hostname
|
111
|
+
Hector.server_name
|
112
|
+
end
|
113
|
+
|
114
|
+
def name
|
115
|
+
nickname
|
116
|
+
end
|
117
|
+
|
118
|
+
def realname
|
119
|
+
nickname
|
120
|
+
end
|
121
|
+
|
122
|
+
def receive(request)
|
123
|
+
@request = request
|
124
|
+
if respond_to?(@request.event_name)
|
125
|
+
send(@request.event_name)
|
126
|
+
end
|
127
|
+
ensure
|
128
|
+
@request = nil
|
129
|
+
end
|
130
|
+
|
131
|
+
def rename(new_nickname)
|
132
|
+
Session.rename(nickname, new_nickname)
|
133
|
+
@nickname = new_nickname
|
134
|
+
end
|
135
|
+
|
136
|
+
def respond_with(command, *args)
|
137
|
+
@response = command.is_a?(Response) ? command : Response.new(command, *preprocess_args(args))
|
138
|
+
if respond_to?(@response.event_name)
|
139
|
+
send(@response.event_name)
|
140
|
+
end
|
141
|
+
@response
|
142
|
+
ensure
|
143
|
+
@response = nil
|
144
|
+
end
|
145
|
+
|
146
|
+
def away(away_message)
|
147
|
+
@away_message = away_message
|
148
|
+
end
|
149
|
+
|
150
|
+
def away?
|
151
|
+
!@away_message.nil?
|
152
|
+
end
|
153
|
+
|
154
|
+
def back
|
155
|
+
@away_message = nil
|
156
|
+
end
|
157
|
+
|
158
|
+
def source
|
159
|
+
"#{nickname}!#{username}@#{hostname}"
|
160
|
+
end
|
161
|
+
|
162
|
+
def username
|
163
|
+
"~#{nickname}"
|
164
|
+
end
|
165
|
+
|
166
|
+
def who
|
167
|
+
"#{identity.username} #{Hector.server_name} #{Hector.server_name} #{nickname} H :0 #{realname}"
|
168
|
+
end
|
169
|
+
|
170
|
+
protected
|
171
|
+
def destination_klass_for(name)
|
172
|
+
name =~ /^#/ ? Channel : Session
|
173
|
+
end
|
174
|
+
|
175
|
+
def preprocess_args(args)
|
176
|
+
args.map do |arg|
|
177
|
+
if arg.is_a?(Symbol) && arg.to_s[0, 1] == "$"
|
178
|
+
send(arg.to_s[1..-1])
|
179
|
+
else
|
180
|
+
arg
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Hector
|
2
|
+
class UserSession < Session
|
3
|
+
attr_reader :connection, :identity, :realname
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def create(nickname, connection, identity, realname)
|
7
|
+
if find(nickname)
|
8
|
+
raise NicknameInUse, nickname
|
9
|
+
else
|
10
|
+
register UserSession.new(nickname, connection, identity, realname)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(nickname, connection, identity, realname)
|
16
|
+
super(nickname)
|
17
|
+
@connection = connection
|
18
|
+
@identity = identity
|
19
|
+
@realname = realname
|
20
|
+
initialize_keep_alive
|
21
|
+
initialize_presence
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy
|
25
|
+
super
|
26
|
+
destroy_presence
|
27
|
+
destroy_keep_alive
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_with(*)
|
31
|
+
connection.respond_with(super)
|
32
|
+
end
|
33
|
+
|
34
|
+
def username
|
35
|
+
identity.username
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Hector
|
2
|
+
# Identity adapters must implement the following public methods:
|
3
|
+
# - authenticate(username, password)
|
4
|
+
# - remember(username, password)
|
5
|
+
# - forget(username)
|
6
|
+
# - normalize(username)
|
7
|
+
#
|
8
|
+
class YamlIdentityAdapter
|
9
|
+
attr_reader :filename
|
10
|
+
|
11
|
+
def initialize(filename)
|
12
|
+
@filename = File.expand_path(filename)
|
13
|
+
end
|
14
|
+
|
15
|
+
def authenticate(username, password)
|
16
|
+
yield load_identities[normalize(username)] == hash(normalize(username), password)
|
17
|
+
end
|
18
|
+
|
19
|
+
def remember(username, password)
|
20
|
+
identities = load_identities
|
21
|
+
identities[normalize(username)] = hash(normalize(username), password)
|
22
|
+
store_identities(identities)
|
23
|
+
end
|
24
|
+
|
25
|
+
def forget(username)
|
26
|
+
identities = load_identities
|
27
|
+
identities.delete(normalize(username))
|
28
|
+
store_identities(identities)
|
29
|
+
end
|
30
|
+
|
31
|
+
def normalize(username)
|
32
|
+
username.strip.downcase
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
def load_identities
|
37
|
+
ensure_file_exists
|
38
|
+
YAML.load_file(filename) || {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def store_identities(identities)
|
42
|
+
File.open(filename, "w") do |file|
|
43
|
+
file.puts(identities.to_yaml)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def hash(username, password)
|
48
|
+
Digest::SHA1.hexdigest(Digest::SHA1.hexdigest(username) + password)
|
49
|
+
end
|
50
|
+
|
51
|
+
def ensure_file_exists
|
52
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
53
|
+
FileUtils.touch(filename)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hector
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Sam Stephenson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-12-04 00:00:00 -06:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: eventmachine
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 59
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 12
|
33
|
+
- 10
|
34
|
+
version: 0.12.10
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: mocha
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 41
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 9
|
49
|
+
- 9
|
50
|
+
version: 0.9.9
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
description: A private group chat server for people you trust. Implements a limited subset of the IRC protocol.
|
54
|
+
email:
|
55
|
+
- sstephenson@gmail.com
|
56
|
+
executables: []
|
57
|
+
|
58
|
+
extensions: []
|
59
|
+
|
60
|
+
extra_rdoc_files: []
|
61
|
+
|
62
|
+
files:
|
63
|
+
- lib/hector/boot.rb
|
64
|
+
- lib/hector/channel.rb
|
65
|
+
- lib/hector/commands/away.rb
|
66
|
+
- lib/hector/commands/join.rb
|
67
|
+
- lib/hector/commands/mode.rb
|
68
|
+
- lib/hector/commands/names.rb
|
69
|
+
- lib/hector/commands/nick.rb
|
70
|
+
- lib/hector/commands/notice.rb
|
71
|
+
- lib/hector/commands/part.rb
|
72
|
+
- lib/hector/commands/ping.rb
|
73
|
+
- lib/hector/commands/pong.rb
|
74
|
+
- lib/hector/commands/privmsg.rb
|
75
|
+
- lib/hector/commands/quit.rb
|
76
|
+
- lib/hector/commands/realname.rb
|
77
|
+
- lib/hector/commands/topic.rb
|
78
|
+
- lib/hector/commands/who.rb
|
79
|
+
- lib/hector/commands/whois.rb
|
80
|
+
- lib/hector/concerns/authentication.rb
|
81
|
+
- lib/hector/concerns/keep_alive.rb
|
82
|
+
- lib/hector/concerns/presence.rb
|
83
|
+
- lib/hector/connection.rb
|
84
|
+
- lib/hector/deference.rb
|
85
|
+
- lib/hector/errors.rb
|
86
|
+
- lib/hector/heartbeat.rb
|
87
|
+
- lib/hector/identity.rb
|
88
|
+
- lib/hector/logging.rb
|
89
|
+
- lib/hector/request.rb
|
90
|
+
- lib/hector/response.rb
|
91
|
+
- lib/hector/server.rb
|
92
|
+
- lib/hector/service.rb
|
93
|
+
- lib/hector/session.rb
|
94
|
+
- lib/hector/user_session.rb
|
95
|
+
- lib/hector/version.rb
|
96
|
+
- lib/hector/yaml_identity_adapter.rb
|
97
|
+
- lib/hector.rb
|
98
|
+
has_rdoc: true
|
99
|
+
homepage: http://github.com/sstephenson/hector
|
100
|
+
licenses: []
|
101
|
+
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
hash: 3
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
version: "0"
|
125
|
+
requirements: []
|
126
|
+
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.3.7
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: Private group chat server
|
132
|
+
test_files: []
|
133
|
+
|