hector 1.0.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/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
|
+
|