autumn 3.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +11 -0
- data/CHANGELOG +567 -0
- data/MANIFEST +110 -0
- data/README +1114 -0
- data/README.textile +1153 -0
- data/Rakefile +75 -0
- data/autumn.gemspec +44 -0
- data/bin/autumn +11 -0
- data/lib/autumn.rb +8 -0
- data/lib/autumn/authentication.rb +238 -0
- data/lib/autumn/channel_leaf.rb +107 -0
- data/lib/autumn/coder.rb +166 -0
- data/lib/autumn/console_boot.rb +10 -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 +190 -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/resources/daemons/Anothernet.yml +3 -0
- data/lib/autumn/resources/daemons/AustHex.yml +29 -0
- data/lib/autumn/resources/daemons/Bahamut.yml +67 -0
- data/lib/autumn/resources/daemons/Dancer.yml +3 -0
- data/lib/autumn/resources/daemons/GameSurge.yml +3 -0
- data/lib/autumn/resources/daemons/IRCnet.yml +3 -0
- data/lib/autumn/resources/daemons/Ithildin.yml +7 -0
- data/lib/autumn/resources/daemons/KineIRCd.yml +56 -0
- data/lib/autumn/resources/daemons/PTlink.yml +6 -0
- data/lib/autumn/resources/daemons/QuakeNet.yml +20 -0
- data/lib/autumn/resources/daemons/RFC1459.yml +158 -0
- data/lib/autumn/resources/daemons/RFC2811.yml +16 -0
- data/lib/autumn/resources/daemons/RFC2812.yml +36 -0
- data/lib/autumn/resources/daemons/RatBox.yml +25 -0
- data/lib/autumn/resources/daemons/Ultimate.yml +24 -0
- data/lib/autumn/resources/daemons/Undernet.yml +6 -0
- data/lib/autumn/resources/daemons/Unreal.yml +110 -0
- data/lib/autumn/resources/daemons/_Other.yml +7 -0
- data/lib/autumn/resources/daemons/aircd.yml +33 -0
- data/lib/autumn/resources/daemons/bdq-ircd.yml +3 -0
- data/lib/autumn/resources/daemons/hybrid.yml +38 -0
- data/lib/autumn/resources/daemons/ircu.yml +67 -0
- data/lib/autumn/resources/daemons/tr-ircd.yml +8 -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/lib/autumn/tool/bin.rb +301 -0
- data/lib/autumn/tool/create.rb +48 -0
- data/lib/autumn/tool/project_creator.rb +110 -0
- data/lib/autumn/version.rb +3 -0
- data/lib/skel/Rakefile +163 -0
- data/lib/skel/config/global.yml +2 -0
- data/lib/skel/config/seasons/testing/database.yml +4 -0
- data/lib/skel/config/seasons/testing/leaves.yml +9 -0
- data/lib/skel/config/seasons/testing/season.yml +2 -0
- data/lib/skel/config/seasons/testing/stems.yml +10 -0
- data/lib/skel/leaves/administrator/README +20 -0
- data/lib/skel/leaves/administrator/controller.rb +67 -0
- data/lib/skel/leaves/administrator/views/autumn.txt.erb +1 -0
- data/lib/skel/leaves/administrator/views/reload.txt.erb +11 -0
- data/lib/skel/leaves/insulter/README +17 -0
- data/lib/skel/leaves/insulter/controller.rb +65 -0
- data/lib/skel/leaves/insulter/views/about.txt.erb +1 -0
- data/lib/skel/leaves/insulter/views/help.txt.erb +1 -0
- data/lib/skel/leaves/insulter/views/insult.txt.erb +1 -0
- data/lib/skel/leaves/scorekeeper/README +34 -0
- data/lib/skel/leaves/scorekeeper/config.yml +2 -0
- data/lib/skel/leaves/scorekeeper/controller.rb +104 -0
- data/lib/skel/leaves/scorekeeper/helpers/general.rb +64 -0
- data/lib/skel/leaves/scorekeeper/models/channel.rb +12 -0
- data/lib/skel/leaves/scorekeeper/models/person.rb +14 -0
- data/lib/skel/leaves/scorekeeper/models/pseudonym.rb +11 -0
- data/lib/skel/leaves/scorekeeper/models/score.rb +14 -0
- data/lib/skel/leaves/scorekeeper/tasks/stats.rake +17 -0
- data/lib/skel/leaves/scorekeeper/views/about.txt.erb +1 -0
- data/lib/skel/leaves/scorekeeper/views/change.txt.erb +5 -0
- data/lib/skel/leaves/scorekeeper/views/history.txt.erb +11 -0
- data/lib/skel/leaves/scorekeeper/views/points.txt.erb +5 -0
- data/lib/skel/leaves/scorekeeper/views/usage.txt.erb +1 -0
- data/lib/skel/log/README +1 -0
- data/lib/skel/script/console +28 -0
- data/lib/skel/script/destroy +48 -0
- data/lib/skel/script/generate +48 -0
- data/lib/skel/shared/README +1 -0
- data/lib/skel/tmp/README +1 -0
- data/spec/authentication_spec.rb +328 -0
- data/spec/channel_leaf_spec.rb +142 -0
- data/spec/coder_spec.rb +146 -0
- data/spec/ctcp_spec.rb +222 -0
- data/spec/daemon_spec.rb +202 -0
- data/spec/datamapper_hacks_spec.rb +164 -0
- data/tasks/authors.rake +30 -0
- data/tasks/changelog.rake +18 -0
- data/tasks/copyright.rake +21 -0
- data/tasks/doc.rake +7 -0
- data/tasks/gem.rake +23 -0
- data/tasks/gem_installer.rake +76 -0
- data/tasks/install_dependencies.rake +6 -0
- data/tasks/manifest.rake +4 -0
- data/tasks/rcov.rake +23 -0
- data/tasks/release.rake +52 -0
- data/tasks/reversion.rake +8 -0
- data/tasks/setup.rake +24 -0
- data/tasks/spec.rake +7 -0
- data/tasks/yard.rake +4 -0
- metadata +188 -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,10 @@
|
|
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 AL_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
|
+
AL_ROOT = ENV["AUTUMN_ROOT"] || File.expand_path("#{File.dirname __FILE__}/..")
|
9
|
+
@genesis = Autumn::Genesis.new
|
10
|
+
@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
|