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.
@@ -0,0 +1,42 @@
1
+ require "digest/sha1"
2
+ require "eventmachine"
3
+ require "fileutils"
4
+ require "socket"
5
+ require "yaml"
6
+
7
+ require "hector/errors"
8
+
9
+ require "hector/concerns/authentication"
10
+ require "hector/concerns/keep_alive"
11
+ require "hector/concerns/presence"
12
+
13
+ require "hector/commands/away"
14
+ require "hector/commands/join"
15
+ require "hector/commands/mode"
16
+ require "hector/commands/names"
17
+ require "hector/commands/nick"
18
+ require "hector/commands/notice"
19
+ require "hector/commands/part"
20
+ require "hector/commands/ping"
21
+ require "hector/commands/pong"
22
+ require "hector/commands/privmsg"
23
+ require "hector/commands/quit"
24
+ require "hector/commands/realname"
25
+ require "hector/commands/topic"
26
+ require "hector/commands/who"
27
+ require "hector/commands/whois"
28
+
29
+ require "hector/channel"
30
+ require "hector/connection"
31
+ require "hector/deference"
32
+ require "hector/heartbeat"
33
+ require "hector/identity"
34
+ require "hector/logging"
35
+ require "hector/request"
36
+ require "hector/response"
37
+ require "hector/server"
38
+ require "hector/session"
39
+ require "hector/service"
40
+ require "hector/user_session"
41
+ require "hector/version"
42
+ require "hector/yaml_identity_adapter"
@@ -0,0 +1,62 @@
1
+ require "pathname"
2
+ require "logger"
3
+
4
+ module Hector
5
+ class << self
6
+ attr_accessor :lib, :root
7
+
8
+ def start
9
+ raise LoadError, "please specify HECTOR_ROOT" unless Hector.root
10
+ load_defaults
11
+ load_application
12
+ end
13
+
14
+ def load_root
15
+ if root = ENV["HECTOR_ROOT"]
16
+ Hector.root = Pathname.new(File.expand_path(root))
17
+ else
18
+ Hector.root = find_application_root_from(Dir.pwd)
19
+ end
20
+ end
21
+
22
+ def load_defaults
23
+ log_path = Hector.root.join("log/hector.log")
24
+ Hector.logger = Logger.new(log_path)
25
+ Hector.logger.datetime_format = "%Y-%m-%d %H:%M:%S"
26
+
27
+ identities_path = Hector.root.join("config/identities.yml")
28
+ Hector::Identity.adapter = Hector::YamlIdentityAdapter.new(identities_path)
29
+ end
30
+
31
+ def load_application
32
+ $:.unshift Hector.root.join("lib")
33
+ load Hector.root.join("init.rb")
34
+ end
35
+
36
+ def find_application_root_from(working_directory)
37
+ dir = Pathname.new(working_directory)
38
+ dir = dir.parent while dir != dir.parent && dir.basename.to_s !~ /\.hect$/
39
+ dir == dir.parent ? nil : dir
40
+ end
41
+ end
42
+ end
43
+
44
+ Hector.lib = Pathname.new(File.expand_path(File.dirname(__FILE__) + "/.."))
45
+ Hector.load_root
46
+
47
+ if Hector.root
48
+ vendor_lib = Hector.root.join("vendor/hector/lib")
49
+ Hector.lib = vendor_lib if vendor_lib.exist?
50
+ end
51
+
52
+ $:.unshift Hector.lib
53
+
54
+ begin
55
+ require "hector"
56
+ rescue LoadError => e
57
+ if require "rubygems"
58
+ retry
59
+ else
60
+ raise e
61
+ end
62
+ end
@@ -0,0 +1,106 @@
1
+ module Hector
2
+ class Channel
3
+ attr_reader :name, :topic, :user_sessions, :created_at
4
+
5
+ class << self
6
+ def find(name)
7
+ channels[normalize(name)]
8
+ end
9
+
10
+ def find_all_for_session(session)
11
+ channels.values.find_all do |channel|
12
+ channel.has_session?(session)
13
+ end
14
+ end
15
+
16
+ def create(name)
17
+ new(name).tap do |channel|
18
+ channels[normalize(name)] = channel
19
+ end
20
+ end
21
+
22
+ def find_or_create(name)
23
+ find(name) || create(name)
24
+ end
25
+
26
+ def delete(name)
27
+ channels.delete(name)
28
+ end
29
+
30
+ def normalize(name)
31
+ if name =~ /^#\w[\w-]{0,15}$/u
32
+ name.downcase
33
+ else
34
+ raise NoSuchChannel, name
35
+ end
36
+ end
37
+
38
+ def reset!
39
+ @channels = nil
40
+ end
41
+
42
+ protected
43
+ def channels
44
+ @channels ||= {}
45
+ end
46
+ end
47
+
48
+ def initialize(name)
49
+ @name = name
50
+ @user_sessions = []
51
+ @created_at = Time.now
52
+ end
53
+
54
+ def broadcast(command, *args)
55
+ catch :stop do
56
+ Session.broadcast_to(sessions, command, *args)
57
+ end
58
+ end
59
+
60
+ def change_topic(session, topic)
61
+ @topic = { :body => topic, :nickname => session.nickname, :time => Time.now }
62
+ end
63
+
64
+ def channel?
65
+ true
66
+ end
67
+
68
+ def deliver(message_type, session, options)
69
+ if has_session?(session)
70
+ broadcast(message_type, name, options.merge(:except => session))
71
+ else
72
+ raise CannotSendToChannel, name
73
+ end
74
+ end
75
+
76
+ def destroy
77
+ self.class.delete(name)
78
+ end
79
+
80
+ def has_session?(session)
81
+ sessions.include?(session)
82
+ end
83
+
84
+ def join(session)
85
+ return if has_session?(session)
86
+ user_sessions.push(session)
87
+ end
88
+
89
+ def nicknames
90
+ user_sessions.map { |session| session.nickname }
91
+ end
92
+
93
+ def part(session)
94
+ user_sessions.delete(session)
95
+ destroy if user_sessions.empty?
96
+ end
97
+
98
+ def services
99
+ Service.all
100
+ end
101
+
102
+ def sessions
103
+ services + user_sessions
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,16 @@
1
+ module Hector
2
+ module Commands
3
+ module Away
4
+ def on_away
5
+ away_message = request.args.first
6
+ if away_message and !away_message.empty?
7
+ @away_message = away_message
8
+ respond_with("306", :text => "You have been marked as being away")
9
+ else
10
+ @away_message = nil
11
+ respond_with("305", :text => "You are no longer marked as being away")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Hector
2
+ module Commands
3
+ module Join
4
+ def on_join
5
+ request.args.first.split(",").each do |channel_name|
6
+ channel = Channel.find_or_create(channel_name)
7
+ if channel.join(self)
8
+ channel.broadcast(:join, :source => source, :text => channel.name)
9
+ respond_to_topic(channel)
10
+ respond_to_names(channel)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ module Hector
2
+ module Commands
3
+ module Mode
4
+ def on_mode
5
+ subject = find(request.args.first)
6
+
7
+ if subject.channel?
8
+ if requesting_modes?
9
+ respond_with("324", nickname, subject.name, "+", :source => Hector.server_name)
10
+ respond_with("329", nickname, subject.name, subject.created_at.to_i, :source => Hector.server_name)
11
+ elsif requesting_bans?
12
+ respond_with("368", nickname, subject.name, :text => "End of Channel Ban List", :source => Hector.server_name)
13
+ end
14
+ else
15
+ if requesting_modes?
16
+ respond_with("221", nickname, "+", :source => Hector.server_name)
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+ def requesting_modes?
23
+ request.args.length == 1
24
+ end
25
+
26
+ def requesting_bans?
27
+ request.args.length == 2 && request.args.last[/^\+?b$/]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module Hector
2
+ module Commands
3
+ module Names
4
+ def on_names
5
+ channel = Channel.find(request.args.first)
6
+ respond_to_names(channel)
7
+ end
8
+
9
+ def respond_to_names(channel)
10
+ responses = Response.apportion_text(channel.nicknames, "353", nickname, "=", channel.name, :source => Hector.server_name)
11
+ responses.each { |response| respond_with(response) }
12
+ respond_with("366", nickname, channel.name, :source => Hector.server_name, :text => "End of /NAMES list.")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Hector
2
+ module Commands
3
+ module Nick
4
+ def on_nick
5
+ old_nickname = nickname
6
+ rename(request.args.first)
7
+ broadcast(:nick, nickname, :source => old_nickname)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Hector
2
+ module Commands
3
+ module Notice
4
+ def on_notice
5
+ touch_presence
6
+ find(request.args.first).deliver(:notice, self, :source => source, :text => request.text)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Hector
2
+ module Commands
3
+ module Part
4
+ def on_part
5
+ channel = Channel.find(request.args.first)
6
+ channel.broadcast(:part, channel.name, :source => source, :text => request.text)
7
+ channel.part(self)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Hector
2
+ module Commands
3
+ module Ping
4
+ def on_ping
5
+ respond_with(:pong, :source => Hector.server_name, :text => request.text)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Hector
2
+ module Commands
3
+ module Pong
4
+ def on_pong
5
+ @received_pong = true
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Hector
2
+ module Commands
3
+ module Privmsg
4
+ def on_privmsg
5
+ touch_presence
6
+ subject = find(request.args.first)
7
+
8
+ subject.deliver(:privmsg, self, :source => source, :text => request.text)
9
+
10
+ if !subject.channel? and subject.away?
11
+ respond_with("301", subject.nickname, :text => subject.away_message)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ module Hector
2
+ module Commands
3
+ module Quit
4
+ def on_quit
5
+ @quit_message = "Quit: #{request.text}"
6
+ connection.close_connection(true)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Hector
2
+ module Commands
3
+ module Realname
4
+ def on_realname
5
+ @realname = request.text
6
+ broadcast("352", :$nickname, "*", who, :source => Hector.server_name)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ module Hector
2
+ module Commands
3
+ module Topic
4
+ def on_topic
5
+ channel = Channel.find(request.args.first)
6
+
7
+ if request.args.length > 1
8
+ topic = request.text
9
+ channel.change_topic(self, topic)
10
+ channel.broadcast(:topic, channel.name, :source => source, :text => topic)
11
+ else
12
+ respond_to_topic(channel)
13
+ end
14
+ end
15
+
16
+ def respond_to_topic(channel)
17
+ if topic = channel.topic
18
+ respond_with("332", nickname, channel.name, :source => Hector.server_name, :text => topic[:body])
19
+ respond_with("333", nickname, channel.name, topic[:nickname], topic[:time].to_i, :source => Hector.server_name)
20
+ else
21
+ respond_with("331", nickname, channel.name, :source => Hector.server_name, :text => "No topic is set.")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ module Hector
2
+ module Commands
3
+ module Who
4
+ def on_who
5
+ name = request.args.first
6
+
7
+ if destination = destination_klass_for(name).find(name)
8
+ sessions_for_who(destination).each do |session|
9
+ respond_with("352", nickname, name, session.who, :source => Hector.server_name)
10
+ end
11
+ end
12
+
13
+ respond_with("315", nickname, name, :source => Hector.server_name, :text => "End of /WHO list.")
14
+ end
15
+
16
+ def sessions_for_who(destination)
17
+ if destination.respond_to?(:sessions)
18
+ destination.sessions
19
+ else
20
+ [destination]
21
+ end
22
+ end
23
+
24
+ def who
25
+ "#{username} #{Hector.server_name} #{Hector.server_name} #{nickname} H :0 #{realname}"
26
+ end
27
+ end
28
+ end
29
+ end