comboy-autumn 3.1
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/README.textile +1192 -0
- data/autumn.gemspec +25 -0
- data/bin/autumn +27 -0
- data/lib/autumn.rb +2 -0
- data/lib/autumn/authentication.rb +290 -0
- data/lib/autumn/channel_leaf.rb +107 -0
- data/lib/autumn/coder.rb +166 -0
- data/lib/autumn/console_boot.rb +9 -0
- data/lib/autumn/ctcp.rb +250 -0
- data/lib/autumn/daemon.rb +207 -0
- data/lib/autumn/datamapper_hacks.rb +290 -0
- data/lib/autumn/foliater.rb +231 -0
- data/lib/autumn/formatting.rb +236 -0
- data/lib/autumn/generator.rb +231 -0
- data/lib/autumn/genesis.rb +191 -0
- data/lib/autumn/inheritable_attributes.rb +162 -0
- data/lib/autumn/leaf.rb +738 -0
- data/lib/autumn/log_facade.rb +49 -0
- data/lib/autumn/misc.rb +87 -0
- data/lib/autumn/script.rb +74 -0
- data/lib/autumn/speciator.rb +165 -0
- data/lib/autumn/stem.rb +919 -0
- data/lib/autumn/stem_facade.rb +176 -0
- data/resources/daemons/Anothernet.yml +3 -0
- data/resources/daemons/AustHex.yml +29 -0
- data/resources/daemons/Bahamut.yml +67 -0
- data/resources/daemons/Dancer.yml +3 -0
- data/resources/daemons/GameSurge.yml +3 -0
- data/resources/daemons/IRCnet.yml +3 -0
- data/resources/daemons/Ithildin.yml +7 -0
- data/resources/daemons/KineIRCd.yml +56 -0
- data/resources/daemons/PTlink.yml +6 -0
- data/resources/daemons/QuakeNet.yml +20 -0
- data/resources/daemons/RFC1459.yml +158 -0
- data/resources/daemons/RFC2811.yml +16 -0
- data/resources/daemons/RFC2812.yml +36 -0
- data/resources/daemons/RatBox.yml +25 -0
- data/resources/daemons/Ultimate.yml +24 -0
- data/resources/daemons/Undernet.yml +6 -0
- data/resources/daemons/Unreal.yml +110 -0
- data/resources/daemons/_Other.yml +7 -0
- data/resources/daemons/aircd.yml +33 -0
- data/resources/daemons/bdq-ircd.yml +3 -0
- data/resources/daemons/hybrid.yml +38 -0
- data/resources/daemons/ircu.yml +67 -0
- data/resources/daemons/tr-ircd.yml +8 -0
- data/skel/Rakefile +135 -0
- data/skel/config/global.yml +2 -0
- data/skel/config/seasons/testing/database.yml +7 -0
- data/skel/config/seasons/testing/leaves.yml +7 -0
- data/skel/config/seasons/testing/season.yml +2 -0
- data/skel/config/seasons/testing/stems.yml +9 -0
- data/skel/leaves/administrator/README +20 -0
- data/skel/leaves/administrator/controller.rb +67 -0
- data/skel/leaves/administrator/views/autumn.txt.erb +1 -0
- data/skel/leaves/administrator/views/reload.txt.erb +11 -0
- data/skel/leaves/insulter/README +17 -0
- data/skel/leaves/insulter/controller.rb +65 -0
- data/skel/leaves/insulter/views/about.txt.erb +1 -0
- data/skel/leaves/insulter/views/help.txt.erb +1 -0
- data/skel/leaves/insulter/views/insult.txt.erb +1 -0
- data/skel/leaves/scorekeeper/README +34 -0
- data/skel/leaves/scorekeeper/config.yml +2 -0
- data/skel/leaves/scorekeeper/controller.rb +104 -0
- data/skel/leaves/scorekeeper/helpers/general.rb +64 -0
- data/skel/leaves/scorekeeper/models/channel.rb +12 -0
- data/skel/leaves/scorekeeper/models/person.rb +14 -0
- data/skel/leaves/scorekeeper/models/pseudonym.rb +11 -0
- data/skel/leaves/scorekeeper/models/score.rb +14 -0
- data/skel/leaves/scorekeeper/tasks/stats.rake +17 -0
- data/skel/leaves/scorekeeper/views/about.txt.erb +1 -0
- data/skel/leaves/scorekeeper/views/change.txt.erb +5 -0
- data/skel/leaves/scorekeeper/views/history.txt.erb +11 -0
- data/skel/leaves/scorekeeper/views/points.txt.erb +5 -0
- data/skel/leaves/scorekeeper/views/usage.txt.erb +1 -0
- data/skel/script/console +34 -0
- data/skel/script/daemon +29 -0
- data/skel/script/destroy +48 -0
- data/skel/script/generate +48 -0
- data/skel/script/server +15 -0
- metadata +170 -0
data/lib/autumn/coder.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
# Defines the Autumn::Coder class and its subclasses, which generate template
|
2
|
+
# code for the script/generate utility.
|
3
|
+
|
4
|
+
module Autumn
|
5
|
+
|
6
|
+
# Helper class that generates shell Ruby code. This class knows how to
|
7
|
+
# generate Ruby code for template classes and methods.
|
8
|
+
|
9
|
+
class Coder # :nodoc:
|
10
|
+
# The generated code string.
|
11
|
+
attr :output
|
12
|
+
|
13
|
+
# Creates a new instance with an indent level of 0 and an empty output
|
14
|
+
# string.
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@indent = 0
|
18
|
+
@output = String.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Creates a new class empty class. This method yields another Generator,
|
22
|
+
# which you can populate with the contents of the class, if you wish.
|
23
|
+
# Example:
|
24
|
+
#
|
25
|
+
# gen.klass("Foo") { |foo| foo.method("bar") }
|
26
|
+
#
|
27
|
+
# produces:
|
28
|
+
#
|
29
|
+
# class Foo
|
30
|
+
# def bar
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
|
34
|
+
def klass(name, superclass=nil)
|
35
|
+
if superclass then
|
36
|
+
self << "class #{name} < #{superclass}"
|
37
|
+
else
|
38
|
+
self << "class #{name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
if block_given? then
|
42
|
+
generator = self.class.new
|
43
|
+
yield generator
|
44
|
+
indent!
|
45
|
+
self << generator.output
|
46
|
+
unindent!
|
47
|
+
end
|
48
|
+
|
49
|
+
self << "end"
|
50
|
+
|
51
|
+
return self
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates a new empty method. Any additional parameters are considered to be
|
55
|
+
# the generated method's parameters. They can be symbols/strings (taken to
|
56
|
+
# be the parameter's name), or hashes associating the parameter's name to
|
57
|
+
# its default value.
|
58
|
+
#
|
59
|
+
# This method yields another Generator, which you can populate with the
|
60
|
+
# contents of the method, if you wish. Example:
|
61
|
+
#
|
62
|
+
# gen.method("test", :required, { :optional => 'default' })
|
63
|
+
#
|
64
|
+
# produces:
|
65
|
+
#
|
66
|
+
# def test(required, optional="default")
|
67
|
+
# end
|
68
|
+
|
69
|
+
def method(name, *params)
|
70
|
+
if params.empty? then
|
71
|
+
self << "def #{name}"
|
72
|
+
else
|
73
|
+
self << "def #{name}(#{parameterize params})"
|
74
|
+
end
|
75
|
+
|
76
|
+
if block_given? then
|
77
|
+
generator = self.class.new
|
78
|
+
yield generator
|
79
|
+
indent!
|
80
|
+
self << generator.output
|
81
|
+
unindent!
|
82
|
+
end
|
83
|
+
|
84
|
+
self << "end"
|
85
|
+
|
86
|
+
return self
|
87
|
+
end
|
88
|
+
|
89
|
+
# Increases the indent level for all future lines of code appended to this
|
90
|
+
# Generator.
|
91
|
+
|
92
|
+
def indent!
|
93
|
+
@indent = @indent + 1
|
94
|
+
end
|
95
|
+
|
96
|
+
# Decreases the indent level for all future lines of code appended to this
|
97
|
+
# Generator.
|
98
|
+
|
99
|
+
def unindent!
|
100
|
+
@indent = @indent - 1 unless @indent == 0
|
101
|
+
end
|
102
|
+
|
103
|
+
# Adds a line of code to this Generator, sequentially.
|
104
|
+
|
105
|
+
def <<(str)
|
106
|
+
str.split(/\n/).each do |line|
|
107
|
+
@output << "#{tab}#{line}\n"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def doc=(str)
|
112
|
+
doc_lines = str.line_wrap(80 - tab.size - 2).split("\n")
|
113
|
+
doc_lines.map! { |str| "#{tab}# #{str}\n" }
|
114
|
+
@output = doc_lines.join + "\n" + @output
|
115
|
+
end
|
116
|
+
|
117
|
+
# Appends a blank line to the output.
|
118
|
+
|
119
|
+
def newline!
|
120
|
+
@output << "\n"
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def parameterize(params)
|
126
|
+
param_strs = Array.new
|
127
|
+
params.each do |param|
|
128
|
+
if param.kind_of? Hash and param.size == 1 then
|
129
|
+
name = param.keys.only
|
130
|
+
default = param.values.only
|
131
|
+
raise ArgumentError, "Invalid parameter #{name.inspect}" unless name.respond_to? :to_s and not name.to_s.empty?
|
132
|
+
param_strs << "#{name.to_s}=#{default.inspect}"
|
133
|
+
elsif param.respond_to? :to_s and not param.to_s.empty? then
|
134
|
+
param_strs << param.to_s
|
135
|
+
else
|
136
|
+
raise ArgumentError, "Invalid parameter #{param.inspect}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
return param_strs.join(', ')
|
140
|
+
end
|
141
|
+
|
142
|
+
def tab
|
143
|
+
' ' * @indent
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Generates Autumn-specific code templates like leaves and filters.
|
148
|
+
|
149
|
+
class TemplateCoder < Coder # :nodoc:
|
150
|
+
|
151
|
+
# Generates an Leaf subclass with the given name.
|
152
|
+
|
153
|
+
def leaf(name)
|
154
|
+
controller = klass('Controller', 'Autumn::Leaf') do |leaf|
|
155
|
+
leaf.newline!
|
156
|
+
leaf << '# Typing "!about" displays some basic information about this leaf.'
|
157
|
+
leaf.newline!
|
158
|
+
about_command = leaf.method('about_command', 'stem', 'sender', 'reply_to', 'msg') do |about|
|
159
|
+
about << '# This method renders the file "about.txt.erb"'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
controller.doc = "Controller for the #{name.camelcase} leaf."
|
163
|
+
return controller
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Used by the script/console script to load the Autumn environment when IRb
|
2
|
+
# is executed.
|
3
|
+
|
4
|
+
require 'autumn/genesis'
|
5
|
+
|
6
|
+
# We set APP_ROOT in the script/console file to set it for the scope of that
|
7
|
+
# file. We have to set it again in here for the scope of the IRb session.
|
8
|
+
@genesis = Autumn::Genesis.new
|
9
|
+
@genesis.boot! false
|
data/lib/autumn/ctcp.rb
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
# Defines the Autum::CTCP class, which implements CTCP support.
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Autumn
|
6
|
+
|
7
|
+
# A listener for a Stem that listens for and handles CTCP requests. You can
|
8
|
+
# add CTCP support for your IRC client by instantiating this object and
|
9
|
+
# passing it to the Stem#add_listener method.
|
10
|
+
#
|
11
|
+
# CTCP stands for Client-to-Client Protocol and is a way that IRC clients and
|
12
|
+
# servers can request and transmit more information about each other. Modern
|
13
|
+
# IRC clients all have CTCP support, and many servers expect or assume that
|
14
|
+
# their clients support CTCP. CTCP is also used as a basis for further
|
15
|
+
# extensions to IRC, such as DCC and XDCC.
|
16
|
+
#
|
17
|
+
# This class implements the spec defined at http://www.invlogic.com/irc/ctcp.html
|
18
|
+
#
|
19
|
+
# Because some IRC servers will disconnect clients that send a large number of
|
20
|
+
# messages in a short period of time, this listener will only send one CTCP
|
21
|
+
# reply per second, with up to a maximum of 10 replies waiting in the queue
|
22
|
+
# (after which new requests are ignored). These values can be adjusted in the
|
23
|
+
# initialization options.
|
24
|
+
#
|
25
|
+
# This class acts as a listener plugin: Any of the methods specified below can
|
26
|
+
# be implemented by any other listener, and will be invoked by this listener
|
27
|
+
# when appropriate.
|
28
|
+
#
|
29
|
+
# To respond to incoming CTCP requests, you should implement methods of the
|
30
|
+
# form <tt>ctcp_*_request</tt>, where "*" is replaced with the lowercase name
|
31
|
+
# of the CTCP command. (For example, to handle VERSION requests, implement
|
32
|
+
# +ctcp_version_request+). This method will be invoked whenever a request is
|
33
|
+
# received by the IRC client. It will be given the following parameters:
|
34
|
+
#
|
35
|
+
# 1. the CTCP instance that parsed the request,
|
36
|
+
# 2. the Stem instance that received the request,
|
37
|
+
# 3. the person who sent the request (a hash in the form of that used by Stem;
|
38
|
+
# see Stem#add_listener for more information), and
|
39
|
+
# 4. an array of arguments passed along with the request.
|
40
|
+
#
|
41
|
+
# In addition, you can implement +ctcp_request_received+, which will then be
|
42
|
+
# invoked for any and all incoming CTCP requests. It is passed the following
|
43
|
+
# arguments:
|
44
|
+
#
|
45
|
+
# 1. the name of the request, as a lowercase symbol,
|
46
|
+
# 2. the CTCP instance that parsed the request,
|
47
|
+
# 3. the Stem instance that received the request,
|
48
|
+
# 4. the person who sent the request (a sender hash -- see the Leaf docs), and
|
49
|
+
# 5. an array of arguments passed along with the request.
|
50
|
+
#
|
51
|
+
# This class will by default respond to some incoming CTCP requests and
|
52
|
+
# generate appropriate replies; however, it does not implement any specific
|
53
|
+
# behavior for parsing incoming replies. If you wish to parse replies, you
|
54
|
+
# should implement methods in your listener of the form
|
55
|
+
# <tt>ctcp_*_response</tt>, with the "*" character replaced as above. This
|
56
|
+
# method will be invoked whenever a reply is received by this listener. You
|
57
|
+
# can also implement <tt>ctcp_response_received</tt> just as above. The
|
58
|
+
# parameters for these methods are the same as those listed above.
|
59
|
+
#
|
60
|
+
# Responses are assumed to be any CTCP messages that are sent as a NOTICE (as
|
61
|
+
# opposed to a PRIVMSG). Because they are NOTICEs, your program should not
|
62
|
+
# send a message in response.
|
63
|
+
#
|
64
|
+
# In addition to responding to incoming CTCP requests and replies, your
|
65
|
+
# listener can use its stem to send CTCP requests and replies. See the added
|
66
|
+
# method for more detail.
|
67
|
+
|
68
|
+
class CTCP
|
69
|
+
# Format of an embedded CTCP request.
|
70
|
+
CTCP_REQUEST = /\x01(.+?)\x01/
|
71
|
+
# CTCP commands whose arguments are encoded according to the CTCP spec (as
|
72
|
+
# opposed to other commands, whose arguments are plaintext).
|
73
|
+
ENCODED_COMMANDS = [ 'VERSION', 'PING' ]
|
74
|
+
|
75
|
+
# Creates a new CTCP parser. Options are:
|
76
|
+
#
|
77
|
+
# +reply_queue_size+:: The maximum number of pending replies to store in the
|
78
|
+
# queue, after which new CTCP requests are ignored.
|
79
|
+
# +reply_rate+:: The minimum time, in seconds, between consecutive CTCP
|
80
|
+
# replies.
|
81
|
+
|
82
|
+
def initialize(options={})
|
83
|
+
@options = options
|
84
|
+
@options[:reply_queue_size] ||= 10
|
85
|
+
@options[:reply_rate] ||= 0.25
|
86
|
+
@reply_thread = Hash.new
|
87
|
+
@reply_queue = Hash.new do |hsh, key|
|
88
|
+
hsh[key] = ForgetfulQueue.new(@options[:reply_queue_size])
|
89
|
+
@reply_thread[key] = Thread.new(key) do |stem|
|
90
|
+
loop do #TODO wake thread when stem is quitting so this thread can terminate?
|
91
|
+
reply = @reply_queue[stem].pop
|
92
|
+
stem.notice reply[:recipient], reply[:message]
|
93
|
+
sleep @options[:reply_rate]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
hsh[key]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Parses CTCP requests in a PRIVMSG event.
|
101
|
+
|
102
|
+
def irc_privmsg_event(stem, sender, arguments) # :nodoc:
|
103
|
+
arguments[:message].scan(CTCP_REQUEST).flatten.each do |ctcp|
|
104
|
+
ctcp_args = ctcp.split(' ')
|
105
|
+
request = ctcp_args.shift
|
106
|
+
ctcp_args = ctcp_args.map { |arg| unquote arg } if ENCODED_COMMANDS.include? request
|
107
|
+
meth = "ctcp_#{request.downcase}_request".to_sym
|
108
|
+
stem.broadcast meth, self, stem, sender, ctcp_args
|
109
|
+
stem.broadcast :ctcp_request_received, request.downcase.to_sym, self, stem, sender, ctcp_args
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Parses CTCP responses in a NOTICE event.
|
114
|
+
|
115
|
+
def irc_notice_event(stem, sender, arguments) # :nodoc:
|
116
|
+
arguments[:message].scan(CTCP_REQUEST).flatten.each do |ctcp|
|
117
|
+
ctcp_args = ctcp.split(' ')
|
118
|
+
request = ctcp_args.shift
|
119
|
+
ctcp_args = ctcp_args.map { |arg| unquote arg } if ENCODED_COMMANDS.include? request
|
120
|
+
meth = "ctcp_#{request.downcase}_response".to_sym
|
121
|
+
stem.broadcast meth, self, stem, sender, ctcp_args
|
122
|
+
stem.broadcast :ctcp_response_received, request.downcase.to_sym, self, stem, sender, ctcp_args
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Replies to a CTCP VERSION request by sending:
|
127
|
+
#
|
128
|
+
# * the name of the IRC client ("Autumn, a Ruby IRC framework"),
|
129
|
+
# * the operating system name and version, and
|
130
|
+
# * the home page URL for Autumn.
|
131
|
+
#
|
132
|
+
# To determine the OS name and version, this method runs the <tt>uname
|
133
|
+
# -sr</tt> command; if your operating system does not support this command,
|
134
|
+
# you should override this method.
|
135
|
+
#
|
136
|
+
# Although the CTCP spec states that the VERSION response should be three
|
137
|
+
# encoded strings (as shown above), many modern clients expect one plaintext
|
138
|
+
# string. If you'd prefer compatibility with those clients, you should
|
139
|
+
# override this method to return a single plaintext string and remove the
|
140
|
+
# VERSION command from +ENCODED_COMMANDS+.
|
141
|
+
|
142
|
+
def ctcp_version_request(handler, stem, sender, arguments)
|
143
|
+
return unless handler == self
|
144
|
+
send_ctcp_reply stem, sender[:nick], 'VERSION', "Autumn #{AUTUMN_VERSION}, a Ruby IRC framework", `uname -sr`, "http://github.com/RISCfuture/autumn"
|
145
|
+
end
|
146
|
+
|
147
|
+
# Replies to a CTCP PING request by sending back the same arguments as a
|
148
|
+
# PONG reply.
|
149
|
+
|
150
|
+
def ctcp_ping_request(handler, stem, sender, arguments)
|
151
|
+
return unless handler == self
|
152
|
+
send_ctcp_reply stem, sender[:nick], 'PING', *arguments
|
153
|
+
end
|
154
|
+
|
155
|
+
# Replies to a CTCP TIME request by sending back the local time in RFC-822
|
156
|
+
# format.
|
157
|
+
|
158
|
+
def ctcp_time_request(handler, stem, sender, arguments)
|
159
|
+
return unless handler == self
|
160
|
+
send_ctcp_reply stem, sender[:nick], 'TIME', Time.now.rfc822
|
161
|
+
end
|
162
|
+
|
163
|
+
# Adds a CTCP reply to the queue. You must pass the Stem instance that will
|
164
|
+
# be sending this reply, the recipient (channel or nick), and the name of
|
165
|
+
# the CTCP command (as an uppercase string). Any additional arguments are
|
166
|
+
# taken to be arguments of the CTCP reply, and are thus encoded and joined
|
167
|
+
# by space characters, as specified in the CTCP white paper. The arguments
|
168
|
+
# should all be strings.
|
169
|
+
#
|
170
|
+
# +recipient+ can be a nick, a channel name, or a sender hash, as necessary.
|
171
|
+
# Encoding of arguments is only done for commands in +ENCODED_COMMANDS+.
|
172
|
+
|
173
|
+
def send_ctcp_reply(stem, recipient, command, *arguments)
|
174
|
+
recipient = recipient[:nick] if recipient.kind_of? Hash
|
175
|
+
@reply_queue[stem] << { :recipient => recipient, :message => make_ctcp_message(command, *arguments) }
|
176
|
+
end
|
177
|
+
|
178
|
+
# When this listener is added to a stem, the stem gains the ability to send
|
179
|
+
# CTCP messages directly. Methods of the form <tt>ctcp_*</tt>, where "*" is
|
180
|
+
# the lowercase name of a CTCP action, will be forwarded to this listener,
|
181
|
+
# which will send the CTCP message. The first parameter of the method is the
|
182
|
+
# nick of one or more recipients; all other parameters are parameters for
|
183
|
+
# the CTCP command. See the CTCP spec for more information on the different
|
184
|
+
# commands and parameters available.
|
185
|
+
#
|
186
|
+
# For example, to send an action (such as "/me is cold") to a channel:
|
187
|
+
#
|
188
|
+
# stem.ctcp_action "#channel", "is cold"
|
189
|
+
#
|
190
|
+
# In addition, the stem gains the ability to send CTCP replies. Replies are
|
191
|
+
# messages that are added to this class's reply queue, adding flood abuse
|
192
|
+
# prevention. To send a reply, call a Stem method of the form
|
193
|
+
# <tt>ctcp_reply_*</tt>, where "*" is the command name you are replying to,
|
194
|
+
# in lowercase. Pass first the nick or sender hash of the recipient, then
|
195
|
+
# any parameters as specified by the CTCP spec. For example, to respond to a
|
196
|
+
# CTCP VERSION request:
|
197
|
+
#
|
198
|
+
# stem.ctcp_reply_version sender, 'Bot Name', 'Computer Name', 'Other Info'
|
199
|
+
#
|
200
|
+
# (Note that responding to VERSION requests is already handled by this class
|
201
|
+
# so you'll need to either override or delete the ctcp_version_request
|
202
|
+
# method to do this.)
|
203
|
+
|
204
|
+
def added(stem)
|
205
|
+
stem.instance_variable_set :@ctcp, self
|
206
|
+
class << stem
|
207
|
+
def method_missing(meth, *args)
|
208
|
+
if meth.to_s =~ /^ctcp_reply_([a-z]+)$/ then
|
209
|
+
@ctcp.send_ctcp_reply self, args.shift, $1.to_s.upcase, *args
|
210
|
+
elsif meth.to_s =~ /^ctcp_([a-z]+)$/ then
|
211
|
+
privmsg args.shift, @ctcp.make_ctcp_message($1.to_s.upcase, *args)
|
212
|
+
else
|
213
|
+
super
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Creates a CTCP-formatted message with the given command (uppercase string)
|
220
|
+
# and arguments (strings). The string returned is suitable for transmission
|
221
|
+
# over IRC using the PRIVMSG command.
|
222
|
+
|
223
|
+
def make_ctcp_message(command, *arguments)
|
224
|
+
arguments = arguments.map { |arg| quote arg } if ENCODED_COMMANDS.include? command
|
225
|
+
"\01#{arguments.unshift(command).join(' ')}\01"
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def quote(str)
|
231
|
+
chars = str.split('')
|
232
|
+
chars.map! do |char|
|
233
|
+
case char
|
234
|
+
when "\0" then '\0'
|
235
|
+
when "\1" then '\1'
|
236
|
+
when "\n" then '\n'
|
237
|
+
when "\r" then '\r'
|
238
|
+
when " " then '\@'
|
239
|
+
when "\\" then '\\\\'
|
240
|
+
else char
|
241
|
+
end
|
242
|
+
end
|
243
|
+
return chars.join
|
244
|
+
end
|
245
|
+
|
246
|
+
def unquote(str)
|
247
|
+
str.gsub('\\\\', '\\').gsub('\@', " ").gsub('\r', "\r").gsub('\n', "\n").gsub('\1', "\1").gsub('\0', "\0")
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# Defines the Autumn::Daemon class, which stores information on the different
|
2
|
+
# implementations of IRC by different server daemons.
|
3
|
+
|
4
|
+
module Autumn
|
5
|
+
|
6
|
+
# Describes an IRC server daemon program. Different IRC servers run off of
|
7
|
+
# different IRC daemons, each of which has a slightly different implementation
|
8
|
+
# of the IRC protocol. To encapsulate this, the Daemon class stores the names
|
9
|
+
# of some of the more common IRC server types, as well as the unique
|
10
|
+
# information about those daemons, such as supported usermodes, response
|
11
|
+
# codes, and supported channel types.
|
12
|
+
#
|
13
|
+
# An instance of Daemon is an IRC server type. The Daemon class keeps a
|
14
|
+
# catalog of all instances, assigning each a descriptive name (for example,
|
15
|
+
# "Unreal" for the UnrealIRCd program, a popular IRC server daemon).
|
16
|
+
#
|
17
|
+
# A Daemon instance can be queried for information about the IRC server type,
|
18
|
+
# as necessary to parse messages from that IRC server.
|
19
|
+
#
|
20
|
+
# A "default" daemon will also be created representing a common denominator of
|
21
|
+
# IRC features, which is used in situations where a server's exact type is
|
22
|
+
# unknown. This daemon will consist of all non-conflicting entries among the
|
23
|
+
# defined daemons.
|
24
|
+
#
|
25
|
+
# In addition to the methods and attributes below, you can also call predicate
|
26
|
+
# methods such as <tt>usermode?</tt> and <tt>channel_prefix?</tt> to test if a
|
27
|
+
# character is in a set of known modes/privileges/prefixes, or if a number is
|
28
|
+
# in the set of known events.
|
29
|
+
|
30
|
+
class Daemon
|
31
|
+
|
32
|
+
# Creates a new Daemon instance associated with a given name. You must also
|
33
|
+
# pass in the hashes for it to store.
|
34
|
+
|
35
|
+
def initialize(name, info)
|
36
|
+
if name.nil? and info.nil? then # it's the default hash
|
37
|
+
raise "Already created a default Daemon" if self.class.class_variable_defined? :@@default
|
38
|
+
@usermode = Hash.parroting
|
39
|
+
@privilege = Hash.parroting
|
40
|
+
@user_prefix = Hash.parroting
|
41
|
+
@channel_prefix = Hash.parroting
|
42
|
+
@channel_mode = Hash.parroting
|
43
|
+
@server_mode = Hash.parroting
|
44
|
+
@event = Hash.parroting
|
45
|
+
@default = true
|
46
|
+
else
|
47
|
+
@usermode = Hash.parroting(info['usermode'])
|
48
|
+
@privilege = Hash.parroting(info['privilege'])
|
49
|
+
@user_prefix = Hash.parroting(info['user_prefix'])
|
50
|
+
@channel_prefix = Hash.parroting(info['channel_prefix'])
|
51
|
+
@channel_mode = Hash.parroting(info['channel_mode'])
|
52
|
+
@server_mode = Hash.parroting(info['server_mode'])
|
53
|
+
@event = Hash.parroting(info['event'])
|
54
|
+
@@instances[name] = self
|
55
|
+
|
56
|
+
# Build up our default so it contains all keys with no conflicting
|
57
|
+
# values across different server specs. Delete keys from the default
|
58
|
+
# hash for which we found duplicates.
|
59
|
+
info.each do |hname, hsh|
|
60
|
+
next unless @@default.respond_to? hname.to_sym
|
61
|
+
default_hash = @@default.send(hname.to_sym)
|
62
|
+
|
63
|
+
uniques = hsh.reject { |k, v| default_hash.include? k }
|
64
|
+
default_hash.update uniques
|
65
|
+
default_hash.reject! { |k, v| hsh.include?(k) and hsh[k] != v }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a Daemon instance by associated name.
|
71
|
+
|
72
|
+
def self.[](name)
|
73
|
+
@@instances[name]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the fallback server type.
|
77
|
+
|
78
|
+
def self.default
|
79
|
+
@@default
|
80
|
+
end
|
81
|
+
|
82
|
+
# Yields the name of each Daemon registered with the class.
|
83
|
+
|
84
|
+
def self.each_name
|
85
|
+
@@instances.keys.sort.each { |name| yield name }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Hash of usermode characters (e.g., <tt>i</tt>) to their symbol
|
89
|
+
# representations (e.g., <tt>:invisible</tt>).
|
90
|
+
|
91
|
+
def usermode
|
92
|
+
@default ? @usermode : @@default.usermode.merge(@usermode)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Hash of user privilege characters (e.g., <tt>v</tt>) to their symbol
|
96
|
+
# representations (e.g., <tt>:voiced</tt>).
|
97
|
+
|
98
|
+
def privilege
|
99
|
+
@default ? @privilege : @@default.privilege.merge(@privilege)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Hash of user privilege prefixes (e.g., <tt>@</tt>) to their symbol
|
103
|
+
# representations (e.g., <tt>:operator</tt>).
|
104
|
+
|
105
|
+
def user_prefix
|
106
|
+
@default ? @user_prefix : @@default.user_prefix.merge(@user_prefix)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Hash of channel prefixes (e.g., <tt>&</tt>) to their symbol
|
110
|
+
# representations (e.g., <tt>:local</tt>).
|
111
|
+
|
112
|
+
def channel_prefix
|
113
|
+
@default ? @channel_prefix : @@default.channel_prefix.merge(@channel_prefix)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Hash of channel mode characters (e.g., <tt>m</tt>) to their symbol
|
117
|
+
# representations (e.g., <tt>:moderated</tt>).
|
118
|
+
|
119
|
+
def channel_mode
|
120
|
+
@default ? @channel_mode : @@default.channel_mode.merge(@channel_mode)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Hash of server mode characters (e.g., <tt>H</tt>) to their symbol
|
124
|
+
# representations (e.g., <tt>:hidden</tt>).
|
125
|
+
|
126
|
+
def server_mode
|
127
|
+
@default ? @server_mode : @@default.server_mode.merge(@server_mode)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Hash of numerical event codes (e.g., 372) to their symbol representations
|
131
|
+
# (e.g., <tt>:motd</tt>).
|
132
|
+
|
133
|
+
def event
|
134
|
+
@default ? @event : @@default.event.merge(@event)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns true if the mode string (e.g., "+v") appears to be changing a user
|
138
|
+
# privilege as opposed to a channel mode.
|
139
|
+
|
140
|
+
def privilege_mode?(mode)
|
141
|
+
raise ArgumentError, "Invalid mode string '#{mode}'" unless mode =~ /^[\+\-]\S+$/
|
142
|
+
mode.except_first.chars.all? { |c| privilege? c }
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns true if the first character(s) of a nick are valid privilege
|
146
|
+
# prefixes.
|
147
|
+
|
148
|
+
def nick_prefixed?(nick)
|
149
|
+
user_prefix? nick[0,1]
|
150
|
+
end
|
151
|
+
|
152
|
+
# Given a nick, returns that nick stripped of any privilege characters on
|
153
|
+
# the left side.
|
154
|
+
|
155
|
+
def just_nick(name)
|
156
|
+
nick = name.dup
|
157
|
+
while nick_prefixed?(nick)
|
158
|
+
nick.slice! 0, 1
|
159
|
+
end
|
160
|
+
return nick
|
161
|
+
end
|
162
|
+
|
163
|
+
# Given a nick, returns the privileges granted to this nick, as indicated by
|
164
|
+
# the prefix characters. Returns :unvoiced if no prefix characters are
|
165
|
+
# present. Returns an array of privileges if multiple prefix characters are
|
166
|
+
# present.
|
167
|
+
|
168
|
+
def nick_privilege(name)
|
169
|
+
privs = Set.new
|
170
|
+
nick = name.dup
|
171
|
+
while user_prefix? nick[0,1]
|
172
|
+
privs << user_prefix[nick[0,1]]
|
173
|
+
nick.slice! 0, 1
|
174
|
+
end
|
175
|
+
case privs.size
|
176
|
+
when 0 then :unvoiced
|
177
|
+
when 1 then privs.only
|
178
|
+
else privs
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def method_missing(meth, *args) # :nodoc:
|
183
|
+
if meth.to_s =~ /^([a-z_]+)\?$/ then
|
184
|
+
base = $1
|
185
|
+
if (instance_variables.include?("@#{base}") or instance_variables.include?("@#{base}".to_sym)) and args.size == 1 then
|
186
|
+
if base.end_with?('_prefix') and args.only.kind_of?(Numeric) then
|
187
|
+
arg = args.only.chr
|
188
|
+
else
|
189
|
+
arg = args.only
|
190
|
+
end
|
191
|
+
eval "#{base}.include? #{arg.inspect}"
|
192
|
+
end
|
193
|
+
else
|
194
|
+
super
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def inspect # :nodoc:
|
199
|
+
"#<#{self.class.to_s} #{@@instances.index self}>"
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
@@instances = Hash.new
|
205
|
+
@@default = self.new(nil, nil)
|
206
|
+
end
|
207
|
+
end
|