comboy-autumn 3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/README.textile +1192 -0
  2. data/autumn.gemspec +25 -0
  3. data/bin/autumn +27 -0
  4. data/lib/autumn.rb +2 -0
  5. data/lib/autumn/authentication.rb +290 -0
  6. data/lib/autumn/channel_leaf.rb +107 -0
  7. data/lib/autumn/coder.rb +166 -0
  8. data/lib/autumn/console_boot.rb +9 -0
  9. data/lib/autumn/ctcp.rb +250 -0
  10. data/lib/autumn/daemon.rb +207 -0
  11. data/lib/autumn/datamapper_hacks.rb +290 -0
  12. data/lib/autumn/foliater.rb +231 -0
  13. data/lib/autumn/formatting.rb +236 -0
  14. data/lib/autumn/generator.rb +231 -0
  15. data/lib/autumn/genesis.rb +191 -0
  16. data/lib/autumn/inheritable_attributes.rb +162 -0
  17. data/lib/autumn/leaf.rb +738 -0
  18. data/lib/autumn/log_facade.rb +49 -0
  19. data/lib/autumn/misc.rb +87 -0
  20. data/lib/autumn/script.rb +74 -0
  21. data/lib/autumn/speciator.rb +165 -0
  22. data/lib/autumn/stem.rb +919 -0
  23. data/lib/autumn/stem_facade.rb +176 -0
  24. data/resources/daemons/Anothernet.yml +3 -0
  25. data/resources/daemons/AustHex.yml +29 -0
  26. data/resources/daemons/Bahamut.yml +67 -0
  27. data/resources/daemons/Dancer.yml +3 -0
  28. data/resources/daemons/GameSurge.yml +3 -0
  29. data/resources/daemons/IRCnet.yml +3 -0
  30. data/resources/daemons/Ithildin.yml +7 -0
  31. data/resources/daemons/KineIRCd.yml +56 -0
  32. data/resources/daemons/PTlink.yml +6 -0
  33. data/resources/daemons/QuakeNet.yml +20 -0
  34. data/resources/daemons/RFC1459.yml +158 -0
  35. data/resources/daemons/RFC2811.yml +16 -0
  36. data/resources/daemons/RFC2812.yml +36 -0
  37. data/resources/daemons/RatBox.yml +25 -0
  38. data/resources/daemons/Ultimate.yml +24 -0
  39. data/resources/daemons/Undernet.yml +6 -0
  40. data/resources/daemons/Unreal.yml +110 -0
  41. data/resources/daemons/_Other.yml +7 -0
  42. data/resources/daemons/aircd.yml +33 -0
  43. data/resources/daemons/bdq-ircd.yml +3 -0
  44. data/resources/daemons/hybrid.yml +38 -0
  45. data/resources/daemons/ircu.yml +67 -0
  46. data/resources/daemons/tr-ircd.yml +8 -0
  47. data/skel/Rakefile +135 -0
  48. data/skel/config/global.yml +2 -0
  49. data/skel/config/seasons/testing/database.yml +7 -0
  50. data/skel/config/seasons/testing/leaves.yml +7 -0
  51. data/skel/config/seasons/testing/season.yml +2 -0
  52. data/skel/config/seasons/testing/stems.yml +9 -0
  53. data/skel/leaves/administrator/README +20 -0
  54. data/skel/leaves/administrator/controller.rb +67 -0
  55. data/skel/leaves/administrator/views/autumn.txt.erb +1 -0
  56. data/skel/leaves/administrator/views/reload.txt.erb +11 -0
  57. data/skel/leaves/insulter/README +17 -0
  58. data/skel/leaves/insulter/controller.rb +65 -0
  59. data/skel/leaves/insulter/views/about.txt.erb +1 -0
  60. data/skel/leaves/insulter/views/help.txt.erb +1 -0
  61. data/skel/leaves/insulter/views/insult.txt.erb +1 -0
  62. data/skel/leaves/scorekeeper/README +34 -0
  63. data/skel/leaves/scorekeeper/config.yml +2 -0
  64. data/skel/leaves/scorekeeper/controller.rb +104 -0
  65. data/skel/leaves/scorekeeper/helpers/general.rb +64 -0
  66. data/skel/leaves/scorekeeper/models/channel.rb +12 -0
  67. data/skel/leaves/scorekeeper/models/person.rb +14 -0
  68. data/skel/leaves/scorekeeper/models/pseudonym.rb +11 -0
  69. data/skel/leaves/scorekeeper/models/score.rb +14 -0
  70. data/skel/leaves/scorekeeper/tasks/stats.rake +17 -0
  71. data/skel/leaves/scorekeeper/views/about.txt.erb +1 -0
  72. data/skel/leaves/scorekeeper/views/change.txt.erb +5 -0
  73. data/skel/leaves/scorekeeper/views/history.txt.erb +11 -0
  74. data/skel/leaves/scorekeeper/views/points.txt.erb +5 -0
  75. data/skel/leaves/scorekeeper/views/usage.txt.erb +1 -0
  76. data/skel/script/console +34 -0
  77. data/skel/script/daemon +29 -0
  78. data/skel/script/destroy +48 -0
  79. data/skel/script/generate +48 -0
  80. data/skel/script/server +15 -0
  81. metadata +170 -0
data/autumn.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'autumn'
3
+ s.version = '3.1'
4
+ s.date = '2009-04-04'
5
+
6
+ s.summary = "A Ruby IRC Bot Framework"
7
+ #s.description = ""
8
+
9
+ s.authors = ['Tim Morgan','Kacper Cieśla']
10
+ s.email = ''
11
+ s.homepage = ''
12
+
13
+ s.bindir = 'bin'
14
+ s.executables << 'autumn'
15
+
16
+ s.has_rdoc = true
17
+ s.rdoc_options = ['--main', 'README.textile']
18
+ s.rdoc_options << '--inline-source' << '--charset=UTF-8'
19
+ s.extra_rdoc_files = ['README.textile']
20
+
21
+ # Does not look very cool but github need static lest to build a gem
22
+ s.files = %w{shared README.textile lib lib/autumn.rb lib/autumn lib/autumn/console_boot.rb lib/autumn/log_facade.rb lib/autumn/channel_leaf.rb lib/autumn/datamapper_hacks.rb lib/autumn/authentication.rb lib/autumn/daemon.rb lib/autumn/stem.rb lib/autumn/generator.rb lib/autumn/leaf.rb lib/autumn/coder.rb lib/autumn/misc.rb lib/autumn/ctcp.rb lib/autumn/formatting.rb lib/autumn/foliater.rb lib/autumn/stem_facade.rb lib/autumn/genesis.rb lib/autumn/script.rb lib/autumn/inheritable_attributes.rb lib/autumn/speciator.rb bin bin/autumn README skel skel/tmp skel/log skel/leaves skel/leaves/administrator skel/leaves/administrator/data skel/leaves/administrator/models skel/leaves/administrator/tasks skel/leaves/administrator/README skel/leaves/administrator/controller.rb skel/leaves/administrator/helpers skel/leaves/administrator/views skel/leaves/administrator/views/reload.txt.erb skel/leaves/administrator/views/autumn.txt.erb skel/leaves/insulter skel/leaves/insulter/data skel/leaves/insulter/models skel/leaves/insulter/tasks skel/leaves/insulter/README skel/leaves/insulter/controller.rb skel/leaves/insulter/helpers skel/leaves/insulter/views skel/leaves/insulter/views/help.txt.erb skel/leaves/insulter/views/about.txt.erb skel/leaves/insulter/views/insult.txt.erb skel/leaves/scorekeeper skel/leaves/scorekeeper/data skel/leaves/scorekeeper/models skel/leaves/scorekeeper/models/person.rb skel/leaves/scorekeeper/models/channel.rb skel/leaves/scorekeeper/models/pseudonym.rb skel/leaves/scorekeeper/models/score.rb skel/leaves/scorekeeper/tasks skel/leaves/scorekeeper/tasks/stats.rake skel/leaves/scorekeeper/README skel/leaves/scorekeeper/controller.rb skel/leaves/scorekeeper/helpers skel/leaves/scorekeeper/helpers/general.rb skel/leaves/scorekeeper/config.yml skel/leaves/scorekeeper/views skel/leaves/scorekeeper/views/about.txt.erb skel/leaves/scorekeeper/views/change.txt.erb skel/leaves/scorekeeper/views/history.txt.erb skel/leaves/scorekeeper/views/points.txt.erb skel/leaves/scorekeeper/views/usage.txt.erb skel/config skel/config/seasons skel/config/seasons/testing skel/config/seasons/testing/stems.yml skel/config/seasons/testing/database.yml skel/config/seasons/testing/leaves.yml skel/config/seasons/testing/season.yml skel/config/global.yml skel/Rakefile skel/script skel/script/server skel/script/console skel/script/destroy skel/script/generate skel/script/daemon autumn.gemspec doc resources resources/daemons resources/daemons/tr-ircd.yml resources/daemons/Dancer.yml resources/daemons/_Other.yml resources/daemons/Undernet.yml resources/daemons/QuakeNet.yml resources/daemons/aircd.yml resources/daemons/IRCnet.yml resources/daemons/KineIRCd.yml resources/daemons/RFC2811.yml resources/daemons/Ultimate.yml resources/daemons/hybrid.yml resources/daemons/GameSurge.yml resources/daemons/AustHex.yml resources/daemons/Unreal.yml resources/daemons/Ithildin.yml resources/daemons/bdq-ircd.yml resources/daemons/RFC1459.yml resources/daemons/RFC2812.yml resources/daemons/Anothernet.yml resources/daemons/Bahamut.yml resources/daemons/ircu.yml resources/daemons/RatBox.yml resources/daemons/PTlink.yml}
23
+
24
+ end
25
+
data/bin/autumn ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+
5
+ if ARGV.empty?
6
+ puts "Usage: autumn /path/to/app"
7
+ puts "\nDescription:"
8
+ puts " Creates new autumn app skeleton in the given directory."
9
+ else
10
+ begin
11
+ dir = ARGV[0]
12
+ print "Preparing application skeleton in directory #{dir}... "; STDOUT.flush
13
+ Dir.mkdir dir
14
+ my_dir = File.expand_path(File.dirname(__FILE__))
15
+ FileUtils.cp_r Dir.glob(File.join(my_dir,'..','skel','*')), dir
16
+
17
+ # There's a small problem with packaging empty dirs into a gem
18
+ FileUtils.mkdir_p File.join(dir,'tmp')
19
+ FileUtils.mkdir_p File.join(dir,'log')
20
+ puts "DONE"
21
+
22
+ rescue Errno::EEXIST => e
23
+ puts "Directory already exists."
24
+ exit 1
25
+ end
26
+
27
+ end
data/lib/autumn.rb ADDED
@@ -0,0 +1,2 @@
1
+ AUTUMN_LIB_DIR = File.expand_path(File.dirname(__FILE__))
2
+ $: << AUTUMN_LIB_DIR
@@ -0,0 +1,290 @@
1
+ # Defines the Autumn::Authentication class, which includes different
2
+ # authentication strategies available to leaves.
3
+
4
+ module Autumn
5
+
6
+ # Defines classes which each encapsulate a different strategy for
7
+ # authentication. When the +authentication+ option is specified (see the
8
+ # Autumn::Leaf class), the options given are used to choose the correct class
9
+ # within this module to serve as the authenticator for that leaf.
10
+ #
11
+ # These authentication strategies are used to ensure only authorized users
12
+ # have access to protected commands. Leaf authors can designate certain
13
+ # commands as protected.
14
+ #
15
+ # = Writing Your Own Authenticators
16
+ #
17
+ # When the Autumn::Leaf#authenticate method is called, it converts the symbol
18
+ # from snake_case to CamelCase, and looks for a class in this model. Thus, a
19
+ # call to <tt>authenticate :hostname</tt> would look for a class
20
+ # Autumn::Authentication::Hostname.
21
+ #
22
+ # To define your own authenticator, subclass Autumn::Authentication::Base as a
23
+ # new class in the Autumn::Authentication module. Implement the methods
24
+ # defined in the Autumn::Authentication::Base class docs, then adjust your
25
+ # configuration to use your new authenticator.
26
+
27
+ module Authentication
28
+
29
+ # The basic subclass for all authenticators. If you wish to write your own
30
+ # authenticator, you must subclass this class. You must at a minimum
31
+ # override the authenticate method. You should also override the initialize
32
+ # method if you need to store any options or other data for later use.
33
+ #
34
+ # The authentication module will become a stem listener, so see
35
+ # Autumn::Stem#add_listener for information on other methods you can
36
+ # implement.
37
+
38
+ class Base
39
+
40
+ # Stores the options for this authenticator and configures it for use.
41
+
42
+ def initialize(options={})
43
+ raise "You can only instantiate subclasses of this class."
44
+ end
45
+
46
+ # Returns true if the user is authorized, false if not. +sender+ is a
47
+ # sender hash as defined in the Autumn::Stem docs.
48
+
49
+ def authenticate(stem, channel, sender, leaf)
50
+ raise "Subclasses must override the Autumn::Authentication::Base#authenticate method."
51
+ end
52
+
53
+ # Returns a string to be displayed to a user who is not authorized to
54
+ # perform a command. Override this method to provide more specific hints
55
+ # to a user on what he can do to authorize himself (e.g., "Tell me your
56
+ # password").
57
+
58
+ def unauthorized
59
+ "You must be an administrator for this bot to do that."
60
+ end
61
+ end
62
+
63
+ # Authenticates users by their privilege level in the channel they ran the
64
+ # command in.
65
+ #
66
+ # This is a quick, configuration-free way of protecting your leaf, so long
67
+ # as you trust the ops in your channel.
68
+
69
+ class Op < Base
70
+
71
+ # Creates a new authenticator. Pass a list of allowed privileges (as
72
+ # symbols) for the +privileges+ option. By default this class accepts ops,
73
+ # admins, and channel owners/founders as authorized.
74
+
75
+ def initialize(options={})
76
+ @privileges = options[:privileges]
77
+ @privileges ||= [ :operator, :oper, :op, :admin, :founder, :channel_owner ]
78
+ end
79
+
80
+ def authenticate(stem, channel, sender, leaf) # :nodoc:
81
+ # Returns true if the sender has any of the privileges listed below
82
+ not (@privileges & [ stem.privilege(channel, sender) ].flatten).empty?
83
+ end
84
+
85
+ def unauthorized # :nodoc:
86
+ "You must be an op to do that."
87
+ end
88
+ end
89
+
90
+ # Authenticates by IRC nick. A list of allowed nicks is built on
91
+ # initialization, and anyone with that nick is allowed to use restricted
92
+ # commands.
93
+ #
94
+ # This is the most obvious approach to authentication, but it is hardly
95
+ # secure. Anyone can change their nick to an admin's nick once that admin
96
+ # logs out.
97
+
98
+ class Nick < Base
99
+
100
+ # Creates a new authenticator. Pass a single nick for the +nick+ option or
101
+ # an array of allowed nicks for the +nicks+ option. If neither option is
102
+ # set, an exception is raised.
103
+
104
+ def initialize(options={})
105
+ @nicks = options[:nick]
106
+ @nicks ||= options[:nicks]
107
+ raise "You must give the nick of an administrator to use nick-based authentication." if @nicks.nil?
108
+ @nicks = [ @nicks ] if @nicks.kind_of? String
109
+ end
110
+
111
+ def authenticate(stem, channel, sender, leaf) # :nodoc:
112
+ @nicks.include? sender[:nick]
113
+ end
114
+ end
115
+
116
+ # Works just like nick authentication, the only difference is that one has
117
+ # to be identified to the nickserver.
118
+ #
119
+ # This currently will only work on ircd, dancer and dalnet servers.
120
+
121
+ class RegisteredNick < Base
122
+
123
+ def initialize(options={})
124
+ puts "RegisterNick initialization"
125
+ @nicks = options[:nick]
126
+ @nicks ||= options[:nicks]
127
+ raise "You must must the nick of an administrator to use nick-based authentication." if @nicks.nil?
128
+ @nicks = [ @nicks ] if @nicks.kind_of? String
129
+ @identified = {}
130
+ @whois_lock = Mutex.new
131
+ @identification_lock = Mutex.new
132
+ end
133
+
134
+ def authenticate(stem, channel, sender, leaf) # :nodoc:
135
+ nick = sender[:nick]
136
+ @nicks.include?(nick) && is_identified?(stem,nick)
137
+ end
138
+
139
+ # Respnse code that comes at the end of whois message
140
+ def irc_rpl_endofwhois_response(stem, sender, recipient, arguments, msg)
141
+ @whois_lock.unlock
142
+ end
143
+
144
+ # Dancer and ircd response code including line with identification information
145
+ def irc_rpl_whois_hidden_response(stem, sender, recipient, arguments, msg)
146
+ @identified[arguments.first] = true if msg.strip == "is identified to services"
147
+ end
148
+
149
+ # DALnet response code including line with identification information
150
+ def irc_rpl_whoisregnick_response(stem, sender, recipient, arguments, msg)
151
+ @identified[arguments.first] = true if msg.strip == "has identified for this nick"
152
+ end
153
+
154
+ private
155
+
156
+ def is_identified?(stem,nick)
157
+ @identification_lock.synchronize do
158
+ @whois_lock.lock
159
+ @identified[nick] = false
160
+ stem.whois nick
161
+ @whois_lock.synchronize {}
162
+ @identified[nick]
163
+ end
164
+ end
165
+
166
+ end
167
+
168
+ # Authenticates by the host portion of an IRC message. A hostmask is used to
169
+ # match the relevant portion of the address with a whitelist of accepted
170
+ # host addresses.
171
+ #
172
+ # This method can be a secure way of preventing unauthorized access if you
173
+ # choose an appropriately narrow hostmask. However, you must configure in
174
+ # advance the computers you may want to administrate your leaves from.
175
+
176
+ class Hostname < Base
177
+
178
+ # Creates a new authenticator. You provide a hostmask via the +hostmask+
179
+ # option -- either a Regexp with one capture (that captures the portion of
180
+ # the hostmask you are interested in), or a Proc, which takes a host as an
181
+ # argument and returns true if the host is authorized, false if not. If
182
+ # the +hostmask+ option is not provided, a standard hostmask regexp will
183
+ # be used. This regexp strips everything left of the first period; for the
184
+ # example hostmask "wsd1.ca.widgetcom.net", it would return
185
+ # "ca.widgetcom.net" to be used for comparison.
186
+ #
187
+ # You also provide an authorized host with the +host+ option, or a list of
188
+ # such hosts with the +hosts+ option. If neither is given, an exception is
189
+ # raised.
190
+
191
+ def initialize(options={})
192
+ @hostmask = options[:hostmask]
193
+ @hostmask ||= /^.+?\.(.+)$/
194
+ @hostmask = @hostmask.to_rx(false) if @hostmask.kind_of? String
195
+ if @hostmask.kind_of? Regexp then
196
+ mask = @hostmask
197
+ @hostmask = lambda do |host|
198
+ if matches = host.match(mask) then matches[1] else nil end
199
+ end
200
+ end
201
+
202
+ @hosts = options[:host]
203
+ @hosts ||= options[:hosts]
204
+ raise "You must give the host address of an administrator to use nick-based authentication." unless @hosts
205
+ @hosts = [ @hosts ] unless @hosts.kind_of? Array
206
+ end
207
+
208
+ def authenticate(stem, channel, sender, leaf) # :nodoc:
209
+ @hosts.include? @hostmask.call(sender[:host])
210
+ end
211
+ end
212
+
213
+ # Authenticates by a password provided in secret. When a user PRIVMSG's the
214
+ # leaf the correct password, the leaf adds that user's nick to a list of
215
+ # authorized nicks. These credentials expire when the person changes his
216
+ # nick, logs out, leaves the channel, etc. They also expire if a certain
217
+ # amount of time passes without running any protected commands.
218
+
219
+ class Password < Base
220
+ # The default period of time that must occur with no use of protected
221
+ # commands after which a user's credentials expire.
222
+ DEFAULT_EXPIRE_TIME = 5*60
223
+
224
+ # Creates a new authenticator. You provide a valid password with the
225
+ # +password+ option. If that option is not provided, an exception is
226
+ # raised. You can pass a number of seconds to the +expire_time+ option;
227
+ # this is the amount of time that must pass with no protected commands for
228
+ # a nick's authorization to expire. If the +expire_time+ option is not
229
+ # given, a default value of five minutes is used.
230
+
231
+ def initialize(options={})
232
+ @password = options[:password]
233
+ @expire_time = options[:expire_time]
234
+ @expire_time ||= DEFAULT_EXPIRE_TIME
235
+ raise "You must provide a password to use password-based authentication" unless @password
236
+ @authorized_nicks = Hash.new { |hsh, key| hsh[key] = Set.new }
237
+ @last_protected_action = Hash.new { |hsh, key| hsh[key] = Hash.new(Time.at(0)) }
238
+ @an_lock = Mutex.new
239
+ end
240
+
241
+ def irc_privmsg_event(stem, sender, arguments) # :nodoc:
242
+ if arguments[:recipient] and arguments[:message] == @password then
243
+ @an_lock.synchronize do
244
+ @authorized_nicks[stem] << sender[:nick]
245
+ @last_protected_action[stem][sender[:nick]] = Time.now
246
+ #TODO values are not always deleted; this hash has the possibility to slowly grow and consume more memory
247
+ end
248
+ stem.message "Your password has been accepted, and you are now authorized.", sender[:nick]
249
+ end
250
+ end
251
+
252
+ def irc_nick_event(stem, sender, arguments) # :nodoc:
253
+ @an_lock.synchronize do
254
+ revoke stem, sender[:nick]
255
+ revoke stem, arguments[:nick]
256
+ end
257
+ end
258
+
259
+ def irc_kick_event(stem, sender, arguments) # :nodoc:
260
+ @an_lock.synchronize { revoke stem, arguments[:nick] }
261
+ end
262
+
263
+ def irc_quit_event(stem, sender, arguments) # :nodoc:
264
+ @an_lock.synchronize { revoke stem, sender[:nick] }
265
+ end
266
+
267
+ def authenticate(stem, channel, sender, leaf) # :nodoc:
268
+ @an_lock.synchronize do
269
+ if Time.now - @last_protected_action[stem][sender[:nick]] > @expire_time then
270
+ revoke stem, sender[:nick]
271
+ else
272
+ @last_protected_action[stem][sender[:nick]] = Time.now
273
+ end
274
+ @authorized_nicks[stem].include? sender[:nick]
275
+ end
276
+ end
277
+
278
+ def unauthorized # :nodoc:
279
+ "You must authenticate with an administrator password to do that."
280
+ end
281
+
282
+ private
283
+
284
+ def revoke(stem, nick)
285
+ @authorized_nicks[stem].delete nick
286
+ @last_protected_action[stem].delete nick
287
+ end
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,107 @@
1
+ # Defines the Autumn::ChannelLeaf class, a subclass of Autumn::Leaf that
2
+ # selectively ignores channels.
3
+
4
+ module Autumn
5
+
6
+ # A special kind of leaf that only responds to messages sent to certain
7
+ # channels. Leaves that subclass ChannelLeaf can, in their config, specify a
8
+ # +channels+ option that narrows down which channels the leaf listens to. The
9
+ # leaf will not invoke the hook methods nor the <tt>*_command</tt> methods for
10
+ # IRC events that are not associated with those channels. It will respond to
11
+ # global, non-channel-specific events as well.
12
+ #
13
+ # You can combine multiple ChannelLeaf subclasses in one Stem to allow you to
14
+ # run two leaves off of one nick, but have the nick running different leaves
15
+ # in different channels.
16
+ #
17
+ # The +channels+ option should be a list of stems, and for each stem, a valid
18
+ # channel. For example, if you ran your leaf on two servers, your stems.yml
19
+ # file might look like:
20
+ #
21
+ # GamingServer:
22
+ # channels: fishinggames, games, drivinggames
23
+ # [...]
24
+ # FishingServer:
25
+ # channels: fishinggames, flyfishing
26
+ # [...]
27
+ #
28
+ # Now let's say you had a trivia leaf that asked questions about fishing
29
+ # games. You'd want to run that leaf on the "#fishinggames" channel of each
30
+ # server, and the "#games" channel of the GamingServer, but not the other
31
+ # channels. (Perhaps your Stem was also running other leaves relevant to those
32
+ # channels.) You'd set up your leaves.yml file like so:
33
+ #
34
+ # FishingGamesTrivia:
35
+ # channels:
36
+ # GamingServer:
37
+ # - fishinggames
38
+ # - games
39
+ # FishingServer: fishinggames
40
+ # [...]
41
+ #
42
+ # Now your leaf will only respond to messages relevant to the specified server
43
+ # channels (as well as global messages).
44
+ #
45
+ # Interception and filtering of messages is done at the _leaf_ level, not the
46
+ # _stem_ level. Therefore, for instance, if you override
47
+ # +someone_did_join_channel+, it will only be called for the appropriate
48
+ # channels; however, if you implement +irc_join_event+, it will still be
49
+ # called for all channels the stem is in.
50
+
51
+ class ChannelLeaf < Leaf
52
+ # The IRC channels that this leaf is responding to, mapped to server names.
53
+ attr :channels
54
+
55
+ # Creates a new instance. (See the Leaf class for more information.)
56
+
57
+ def will_start_up
58
+ @channels = Hash.new
59
+ @options[:channels] ||= Hash.new
60
+ @options[:channels].each do |server, chans|
61
+ stem = Foliater.instance.stems[server]
62
+ raise "Unknown stem #{server}" unless stem
63
+ chans = [ chans ] if chans.kind_of? String
64
+ @channels[stem] = chans.map { |chan| stem.normalized_channel_name chan }
65
+ end
66
+ super
67
+ end
68
+
69
+ def irc_privmsg_event(stem, sender, arguments) # :nodoc:
70
+ super if arguments[:channel].nil? or listening?(stem, arguments[:channel])
71
+ end
72
+
73
+ def irc_join_event(stem, sender, arguments) # :nodoc:
74
+ super if listening?(stem, arguments[:channel])
75
+ end
76
+
77
+ def irc_part_event(stem, sender, arguments) # :nodoc:
78
+ super if listening?(stem, arguments[:channel])
79
+ end
80
+
81
+ def irc_mode_event(stem, sender, arguments) # :nodoc:
82
+ super if arguments[:channel].nil? or listening?(stem, arguments[:channel])
83
+ end
84
+
85
+ def irc_topic_event(stem, sender, arguments) # :nodoc:
86
+ super if listening?(stem, arguments[:channel])
87
+ end
88
+
89
+ def irc_invite_event(stem, sender, arguments) # :nodoc:
90
+ super if listening?(stem, arguments[:channel]) or not stem.channels.include? arguments[:channel]
91
+ end
92
+
93
+ def irc_kick_event(stem, sender, arguments) # :nodoc:
94
+ super if listening?(stem, arguments[:channel])
95
+ end
96
+
97
+ def irc_notice_event(stem, sender, arguments) # :nodoc:
98
+ super if arguments[:channel].nil? or listening?(stem, arguments[:channel])
99
+ end
100
+
101
+ private
102
+
103
+ def listening?(stem, channel)
104
+ @channels.include? stem and @channels[stem].include? channel
105
+ end
106
+ end
107
+ end