butler 1.8.1 → 1.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/CHANGELOG.txt +212 -0
  2. data/{README → README.txt} +0 -0
  3. data/Rakefile +16 -11
  4. data/bin/botcontrol +35 -14
  5. data/data/butler/dialogs/create.rb +29 -40
  6. data/data/butler/dialogs/create_config.rb +1 -1
  7. data/data/butler/dialogs/dir.rb +13 -0
  8. data/data/butler/dialogs/en/create.yaml +24 -10
  9. data/data/butler/dialogs/en/dir.yaml +5 -0
  10. data/data/butler/dialogs/en/help.yaml +28 -11
  11. data/data/butler/dialogs/en/quickcreate.yaml +14 -0
  12. data/data/butler/dialogs/help.rb +16 -4
  13. data/data/butler/dialogs/quickcreate.rb +49 -0
  14. data/data/butler/plugins/core/access.rb +211 -0
  15. data/data/butler/plugins/core/logout.rb +11 -11
  16. data/data/butler/plugins/core/plugins.rb +23 -41
  17. data/data/butler/plugins/dev/bleakhouse.rb +46 -0
  18. data/data/butler/plugins/games/roll.rb +1 -1
  19. data/data/butler/plugins/operator/deop.rb +15 -20
  20. data/data/butler/plugins/operator/devoice.rb +14 -20
  21. data/data/butler/plugins/operator/limit.rb +56 -21
  22. data/data/butler/plugins/operator/op.rb +15 -20
  23. data/data/butler/plugins/operator/voice.rb +15 -20
  24. data/data/butler/plugins/service/define.rb +3 -3
  25. data/data/butler/plugins/service/more.rb +40 -0
  26. data/data/butler/plugins/util/cycle.rb +1 -1
  27. data/data/butler/plugins/util/load.rb +5 -5
  28. data/data/butler/plugins/util/pong.rb +3 -2
  29. data/lib/access/privilege.rb +17 -0
  30. data/lib/access/role.rb +33 -2
  31. data/lib/access/savable.rb +6 -0
  32. data/lib/access/yamlbase.rb +1 -2
  33. data/lib/butler/bot.rb +40 -7
  34. data/lib/butler/debuglog.rb +17 -0
  35. data/lib/butler/dialog.rb +1 -1
  36. data/lib/butler/irc/{channels.rb → channellist.rb} +2 -2
  37. data/lib/butler/irc/client.rb +60 -79
  38. data/lib/butler/irc/client/filter.rb +12 -0
  39. data/lib/butler/irc/client/listener.rb +55 -0
  40. data/lib/butler/irc/client/listenerlist.rb +69 -0
  41. data/lib/butler/irc/hostmask.rb +31 -16
  42. data/lib/butler/irc/message.rb +3 -3
  43. data/lib/butler/irc/parser.rb +2 -2
  44. data/lib/butler/irc/parser/rfc2812.rb +2 -6
  45. data/lib/butler/irc/socket.rb +12 -6
  46. data/lib/butler/irc/string.rb +4 -0
  47. data/lib/butler/irc/user.rb +0 -6
  48. data/lib/butler/irc/{users.rb → userlist.rb} +2 -2
  49. data/lib/butler/irc/whois.rb +6 -0
  50. data/lib/butler/plugin.rb +48 -14
  51. data/lib/butler/plugin/configproxy.rb +20 -0
  52. data/lib/butler/plugin/mapper.rb +308 -24
  53. data/lib/butler/plugin/matcher.rb +3 -1
  54. data/lib/butler/plugin/more.rb +65 -0
  55. data/lib/butler/plugin/onhandlers.rb +4 -4
  56. data/lib/butler/plugin/trigger.rb +4 -2
  57. data/lib/butler/plugins.rb +1 -1
  58. data/lib/butler/session.rb +11 -0
  59. data/lib/butler/version.rb +1 -1
  60. data/lib/cloptions.rb +1 -1
  61. data/lib/diagnostics.rb +20 -0
  62. data/lib/dialogline.rb +1 -1
  63. data/lib/durations.rb +19 -6
  64. data/lib/event.rb +8 -5
  65. data/lib/installer.rb +10 -3
  66. data/lib/ostructfixed.rb +11 -0
  67. data/lib/ruby/kernel/daemonize.rb +1 -2
  68. data/test/butler/plugin/mapper.rb +46 -0
  69. metadata +28 -11
  70. data/CHANGELOG +0 -44
  71. data/data/butler/plugins/core/privilege.rb +0 -103
@@ -0,0 +1,12 @@
1
+ class Butler
2
+ module IRC
3
+ class Client
4
+ module Filter # :nodoc:
5
+ attr_accessor :listener
6
+ def unsubscribe
7
+ listener.unsubscribe
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ class Butler
10
+ module IRC
11
+ class Client
12
+
13
+ # Created by Butler::IRC::Client#subscribe() and similar methods
14
+ class Listener
15
+ AllSymbols = [nil].freeze
16
+ include Comparable
17
+
18
+ attr_reader :priority
19
+ attr_reader :symbols
20
+ attr_accessor :container
21
+
22
+ def initialize(symbols=nil, priority=0, *args, &callback)
23
+ @priority = priority
24
+ @symbols = (symbols ? Array(symbols) : AllSymbols).freeze
25
+ @args = args
26
+ @callback = callback
27
+ @container = nil
28
+ end
29
+
30
+ # will remove this listener from the clients dispatcher forever
31
+ def unsubscribe
32
+ @container.unsubscribe(self)
33
+ end
34
+
35
+ # Comparison is done by priority, higher priority comes first, so
36
+ # Listener.new(nil, -100) > Listener.new(nil, 100)
37
+ def <=>(other)
38
+ other.priority <=> @priority
39
+ end
40
+
41
+ # set the priority of this listener
42
+ # see Butler::IRC::Client#subscribe() for infos about priority
43
+ def priority=(value)
44
+ @priority = Integer(value)
45
+ @container.mutated(self)
46
+ end
47
+
48
+ # Invoke the listener, always passes self as first argument
49
+ def call(*args)
50
+ @callback.call(self, *(args+@args))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,69 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'thread'
10
+
11
+
12
+
13
+ class Butler
14
+ module IRC
15
+ class Client
16
+ class ListenerList
17
+ include Enumerable
18
+
19
+ attr_reader :mutex
20
+
21
+ def initialize
22
+ @mutex = Mutex.new
23
+ @all = {}
24
+ @per_symbol = Hash.new { |h,k| h[k] = [] }
25
+ end
26
+
27
+ def each(&block)
28
+ @all.each_key(&block)
29
+ end
30
+
31
+ def synchronized_each_for(symbol, &block)
32
+ @mutex.synchronize {
33
+ @per_symbol[symbol]+@per_symbol[nil]
34
+ }.each(&block)
35
+ end
36
+
37
+ def subscribe(listener)
38
+ @mutex.synchronize {
39
+ raise "#{listener} already subscribed" if @all[listener]
40
+ listener.container = self
41
+ @all[listener] = true
42
+ listener.symbols.each { |s|
43
+ @per_symbol[s] << listener
44
+ @per_symbol[s] = @per_symbol[s].sort_by { |l| -l.priority }
45
+ }
46
+ }
47
+ end
48
+
49
+ def unsubscribe(listener)
50
+ @mutex.synchronize {
51
+ @all.delete(listener)
52
+ listener.symbols.each { |s|
53
+ @per_symbol[s].delete(listener)
54
+ @per_symbol.delete(s) if @per_symbol[s].empty?
55
+ }
56
+ }
57
+ end
58
+
59
+ def mutated(listener)
60
+ @mutex.synchronize {
61
+ listener.symbols.each { |symbol|
62
+ @per_symbol[s] = @per_symbol[s].sort_by { |l| -l.priority }
63
+ }
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -6,48 +6,63 @@
6
6
 
7
7
 
8
8
 
9
- require 'butler/irc/users'
10
-
11
-
12
-
13
9
  class Butler
14
10
  module IRC
15
11
  # Provides methods to see if hostmasks match
16
12
  class Hostmask
13
+
14
+ # List of characters to escape in hostmasks and with what to replace
17
15
  Filter = [
18
16
  [/([\\\[\]{}^`.-])/, '\\\\\1'],
19
17
  [/\?/, "."],
20
18
  [/\*/, ".*?"],
21
19
  ]
22
20
 
23
- # FIXME, not yet correct, escaped * and ?
21
+ # Create a hostmask from a hostmask-string, e.g. "nick!user@host.tld"
22
+ # Also see Butler::IRC::User#hostmask
24
23
  def initialize(mask)
25
- @mask = mask.dup.freeze
26
- filtered = Filter.inject('[]{}^`.-') { |s,(a,b)| s.gsub(a,b) }
27
- #string.gsub(/[\\\x00-\x1f]/) { |match| ("\\%02x" % match[0]) } #implement later
28
- @regex = Regexp.new("\A#{filtered}\z")
24
+ @mask = mask.freeze
25
+
26
+ # FIXME, not yet correct, escaped * and ?
27
+ filtered = Filter.inject(@mask) { |s,(a,b)| s.gsub(a,b) }
28
+ # FIXME implement later
29
+ #string.gsub(/[\\\x00-\x1f]/) { |match| ("\\%02x" % match[0]) }
30
+ n,u,h = filtered.split(/[!@]/)
31
+ @regex = Regexp.new("\A(#{n})!(#{u})@(#{h})\z")
29
32
  end
30
33
 
31
- def matches?(mask)
32
- mask = mask.hostmask if mask.kind_of?(User)
33
- return !@regex.match(mask.to_str).nil?
34
+ # Match a hostmask or anything that responds to #hostmask or #to_str
35
+ # Sets $1-$3 to nick, user and host if matched.
36
+ def =~(mask)
37
+ mask = mask.hostmask if mask.respond_to?(:hostmask)
38
+ !!(@regex =~ mask.to_str)
34
39
  end
35
-
40
+
41
+ # Match a hostmask or anything that responds to #hostmask or #to_str
42
+ # Returns a MatchData instance with 3 captures (nick, user, host)
36
43
  def match(mask)
37
44
  mask = mask.hostmask if mask.kind_of?(User)
38
45
  @regex.match(mask.to_str)
39
46
  end
40
-
47
+ alias === match
48
+
49
+ # return the mask-string
41
50
  def to_str
42
51
  @mask
43
52
  end
44
53
  alias to_s to_str
45
- end
54
+
55
+ def inspect # :nodoc:
56
+ "#<Hostmask #{@mask}>"
57
+ end
58
+ end # Hostmask
46
59
 
47
60
  class User
61
+ # Return the users hostmask, uses wildcards for unknown parts
62
+ # FIXME enable wildcard enforcement
48
63
  def hostmask
49
64
  return Hostmask.new("#{@nick||'*'}!#{@user||'*'}@#{@host||'*'}")
50
65
  end
51
66
  end
52
67
  end
53
- end
68
+ end
@@ -8,16 +8,16 @@
8
8
 
9
9
  #require 'ruby/string' # we need the transcode! method
10
10
 
11
+
12
+
11
13
  class Butler
12
14
  module IRC
13
15
 
14
16
  # Butler::IRC::Message represents messages received by the server.
15
17
  # It provides convenience methods that allow to access information about
16
18
  # those messages easier, e.g. who (as Butler::IRC::User object) sent the
17
- # message in which channel (IRC::Channel object) with what text.
19
+ # message in which channel (Butler::IRC::Channel object) with what text.
18
20
  # Raw message and raw parsed data are still available though.
19
- #
20
- # =FIXME
21
21
  class Message
22
22
  # the command-symbol, see COMMANDS (e.g. :PRIVMSG, :JOIN, ...)
23
23
  attr_reader :symbol
@@ -6,12 +6,12 @@
6
6
 
7
7
 
8
8
 
9
- require 'butler/irc/channels'
9
+ require 'butler/irc/channellist'
10
10
  require 'butler/irc/hostmask'
11
11
  require 'butler/irc/message'
12
12
  require 'butler/irc/parser/commands'
13
13
  require 'butler/irc/string'
14
- require 'butler/irc/users'
14
+ require 'butler/irc/userlist'
15
15
  require 'ruby/exception/detailed'
16
16
  require 'ruby/hash/zip'
17
17
 
@@ -29,8 +29,7 @@ add("kill", :KILL, /^(\S*) (\S*) (.*)/, [:channel, :for, :text]) { |message
29
29
  parser.channels.delete_user(message.for, :kill)
30
30
  end
31
31
  }
32
- # FIXME, really ':?' or just plain ':'?
33
- add("mode", :MODE, /^(\S*) :?(.*)/, [:for, :arguments]) { |message, parser|
32
+ add("mode", :MODE, /^(\S*) (.*)/, [:for, :arguments]) { |message, parser|
34
33
  modifiers = message[:arguments].split(" ")
35
34
  modes = modifiers.shift.split("")
36
35
  flags = {"o" => User::Flags::OP, "v" => User::Flags::VOICE, "u" => User::Flags::UOP}
@@ -190,10 +189,7 @@ add("317", :RPL_WHOISIDLE, /^(\S+) (\S+) ([^:]+) :(.*)/, [:for, :nick, :values,
190
189
  }
191
190
  }
192
191
  add("318", :RPL_ENDOFWHOIS)
193
- add("319", :RPL_WHOISCHANNELS, /^(\S+) (\S+) :(.*)/, [:for, :nick, :channels]) { |message, parser|
194
- # FIXME
195
- message.alter_member(:channels, message.channels.split(" ").map { |channel| parser.channels.create(channel) })
196
- }
192
+ add("319", :RPL_WHOISCHANNELS, /^(\S+) (\S+) :(.*)/, [:for, :nick, :channels]) # only add channels shared with butler to a user - that happens elsewhere already
197
193
  add("321", :RPL_LISTSTART)
198
194
  add("322", :RPL_LIST, /^(\S+) (\S+) (\d+) :(.*)/, [:for, :channelname, :usercount, :topic])
199
195
  add("323", :RPL_LISTEND)
@@ -6,11 +6,12 @@
6
6
 
7
7
 
8
8
 
9
- require 'thread'
10
- require 'socket'
11
- require 'ostructfixed'
9
+ require 'diagnostics'
12
10
  require 'log/comfort'
11
+ require 'ostructfixed'
13
12
  require 'ruby/string/chunks'
13
+ require 'socket'
14
+ require 'thread'
14
15
 
15
16
 
16
17
 
@@ -56,7 +57,7 @@ class Butler
56
57
  VERSION = "1.0.0"
57
58
 
58
59
  include Log::Comfort
59
-
60
+
60
61
  # server the instance is linked with
61
62
  attr_reader :server
62
63
  # port used for connection
@@ -107,7 +108,7 @@ class Butler
107
108
  @limit[key] = options.delete(key) if options.has_key?(key)
108
109
  }
109
110
  @mutex = Mutex.new
110
- @socket = nil
111
+ @socket = Diagnostics.new(self, :write => [NoMethodError, "Must connect first to write to the socket"])
111
112
  @connected = false
112
113
  raise ArgumentError, "Unknown arguments: #{options.keys.inspect}" unless options.empty?
113
114
  end
@@ -205,6 +206,7 @@ class Butler
205
206
  end
206
207
 
207
208
  # FIXME, figure out what the server supports, possibly requires it
209
+ # to be moved to Butler::IRC::Client (to allow ghosting, nickchange, identify)
208
210
  def ghost(nickname, password)
209
211
  write("NS :GHOST #{nickname} #{password}")
210
212
  end
@@ -273,7 +275,6 @@ class Butler
273
275
  end
274
276
 
275
277
  # part specified channels
276
- # FIXME, better way to implement the reason? use a block (yay)?
277
278
  # returns the channels parted from.
278
279
  def part(reason=nil, *channels)
279
280
  if channels.empty?
@@ -310,6 +311,11 @@ class Butler
310
311
  write("KICK #{channel} #{user} :#{reason}")
311
312
  end
312
313
 
314
+ # send a mode command to a channel
315
+ def mode(channel, mode)
316
+ write("MODE #{channel} #{mode}")
317
+ end
318
+
313
319
  # Give Op to user in channel
314
320
  # User can be a nick or IRC::User, either one or an array.
315
321
  def op(channel, *users)
@@ -45,6 +45,10 @@ class String
45
45
  def valid_user?
46
46
  strip_user_prefixes.valid_nickname?
47
47
  end
48
+
49
+ def same_nick?(other)
50
+ strip_user_prefixes.downcase == other.to_str.strip_user_prefixes.downcase
51
+ end
48
52
 
49
53
  # removes indicators from nicknames and channelnames
50
54
  def strip_user_prefixes
@@ -166,7 +166,6 @@ class Butler
166
166
  !common_channels(with_other_user).empty?
167
167
  end
168
168
 
169
- # FIXME
170
169
  # add a channel to the user (should only be used by Butler::IRC::Parser)
171
170
  def add_channel(channel, reason)
172
171
  @channels[channel.to_str.downcase] ||= 0
@@ -176,7 +175,6 @@ class Butler
176
175
  self
177
176
  end
178
177
 
179
- # FIXME
180
178
  # remove a channel from the user (should only be used by Butler::IRC::Parser)
181
179
  def delete_channel(channel, reason)
182
180
  @channels.delete(channel.to_str.downcase)
@@ -185,28 +183,24 @@ class Butler
185
183
  end
186
184
  end
187
185
 
188
- # FIXME
189
186
  def add_flags(channel, flags)
190
187
  channel = channel.to_str.downcase
191
188
  raise ArgumentError, "User #{self} is not listed in #{channel}" unless @channels.include?(channel)
192
189
  @channels[channel] |= flags
193
190
  end
194
191
 
195
- # FIXME
196
192
  def delete_flags(channel, flags)
197
193
  channel = channel.to_str.downcase
198
194
  raise ArgumentError, "User #{self} is not listed in #{channel}" unless @channels.include?(channel)
199
195
  @channels[channel] &= ~flags
200
196
  end
201
197
 
202
- # FIXME
203
198
  def quit
204
199
  @channels.each { |channel, _| delete_channel(channel, :quit) }
205
200
  self.status = :offline
206
201
  @users.delete(self, :quit)
207
202
  end
208
203
 
209
- # FIXME
210
204
  def kill
211
205
  @channels.each { |channel, _| delete_channel(channel, :kill) }
212
206
  self.status = :offline
@@ -6,7 +6,7 @@
6
6
 
7
7
 
8
8
 
9
- require 'butler/irc/channels'
9
+ require 'butler/irc/channellist'
10
10
  require 'butler/irc/string'
11
11
  require 'butler/irc/user'
12
12
  require 'thread'
@@ -16,7 +16,7 @@ require 'thread'
16
16
  class Butler
17
17
  module IRC
18
18
  # Enumerable: all known & visible users
19
- class Users
19
+ class UserList
20
20
  include Enumerable
21
21
 
22
22
  # the user that represents the clients user
@@ -0,0 +1,6 @@
1
+ class Butler
2
+ module IRC
3
+ # Reply for Butler::IRC::Client#whois
4
+ Whois = Struct.new(:exists, :nick, :user, :host, :real, :registered, :channels, :server, :idle, :signon)
5
+ end
6
+ end
@@ -10,6 +10,7 @@ require 'butler/plugin/configproxy'
10
10
  require 'butler/plugin/onhandlers'
11
11
  require 'butler/plugin/mapper'
12
12
  require 'butler/plugin/matcher'
13
+ require 'butler/plugin/more'
13
14
  require 'butler/plugin/trigger'
14
15
  require 'log/comfort'
15
16
  require 'ostructfixed'
@@ -36,15 +37,16 @@ class Butler
36
37
  # this method is called to initialize the plugin-class,
37
38
  # do not override
38
39
  def load_plugin(butler, base, path) # :nodoc:
39
- @butler = butler
40
- @base = base.dup.freeze
41
- @name = File.basename(base).freeze
42
- @commands = []
43
- @listener = []
44
- @config = ConfigProxy.new(@butler.config, "plugin/#{name}")
40
+ @butler = butler
41
+ @base = base.dup.freeze
42
+ @name = File.basename(base).freeze
43
+ @commands = []
44
+ @listener = []
45
+ @mapping_type = Hash.new { |h,k| MappingTypes[k] }
46
+ @config = ConfigProxy.new(@butler.config, "plugin/#{name}")
45
47
 
46
48
  if File.directory?(path) then
47
- raise "Not supported yet" # FIXME
49
+ raise "Not supported yet"
48
50
  @path = OpenStruct.new(
49
51
  :data => (@path+"/data").freeze,
50
52
  :strings => (@path+"/strings").freeze,
@@ -104,12 +106,16 @@ class Butler
104
106
  new(message).usage(data)
105
107
  end
106
108
 
107
- def on_load
109
+ def on_load(*args)
110
+ end
111
+
112
+ def on_login(*args)
108
113
  end
109
114
 
110
115
  def trigger(commands)
111
116
  commands = { "en" => commands } unless commands.kind_of?(Hash)
112
117
  commands.each { |lang, command|
118
+ raise "Invalid trigger, language must be a string" unless String === lang
113
119
  trigger = Trigger.new(self, lang, command)
114
120
  @butler.add_command(trigger)
115
121
  @commands << trigger
@@ -119,6 +125,7 @@ class Butler
119
125
  def map(meth, expressions)
120
126
  expressions = { "en" => expressions } unless expressions.kind_of?(Hash)
121
127
  expressions.each { |lang, expression|
128
+ raise "Invalid map, language must be a string, method a symbol and expression a string" unless String === lang and String === expression and Symbol === meth
122
129
  mapper = Mapper.new(self, meth, lang, expression)
123
130
  @butler.add_command(mapper)
124
131
  @commands << mapper
@@ -142,8 +149,14 @@ class Butler
142
149
  @listener << listener
143
150
  listener
144
151
  end
152
+
153
+ def on_disconnect(*args)
154
+ end
155
+
156
+ def on_quit(*args)
157
+ end
145
158
 
146
- def on_unload
159
+ def on_unload(*args)
147
160
  end
148
161
 
149
162
  def unload_plugin
@@ -205,14 +218,35 @@ class Butler
205
218
  # and translates it using vars as variables for the string interpolation.
206
219
  # The string is subsequently mirc_formatted (see String#mirc_formatted).
207
220
  # Besides that it works like Message#answer.
208
- def answer(string, vars={})
209
- string = localize(string, vars) if string.kind_of?(Symbol)
210
- @message.answer(string.mirc_formatted)
221
+ # Another feature of answer is, that it is 'moreified', that means if your answer-text is longer
222
+ # than 300 chars, butler will display 'more...' at the end and using the plugin 'more' the user
223
+ # can see the rest of the text.
224
+ def answer(text, vars={})
225
+ text = localize(text, vars) if text.kind_of?(Symbol)
226
+ @message.from.session["more"] = More.new(
227
+ @message,
228
+ nil,
229
+ text.mirc_formatted
230
+ )
231
+ @message.answer("#{@message.from.to_s+': ' if @message.public?}#{@message.from.session['more'].show}")
211
232
  end
233
+
234
+ # Same as answer, but 'more' gets a lead prefixed (see Butler::Plugin::More for more info)
235
+ def answer_with_lead(lead, string, vars={})
236
+ text = localize(text, vars) if text.kind_of?(Symbol)
237
+ lead = localize(lead, vars) if text.kind_of?(Symbol)
238
+ @message.from.session["more"] = More.new(
239
+ @message,
240
+ lead.mirc_formatted,
241
+ text.mirc_formatted
242
+ )
243
+ @message.answer("#{@message.from.to_s+': ' if @message.public?}#{@message.from.session['more'].show}")
244
+ end
245
+
212
246
 
213
247
  # == About
214
248
  # Sends a privmsg (localized and formatted) to any number of recipients
215
- # (Users or Channels)
249
+ # (UserList or ChannelList)
216
250
  #
217
251
  # == Synopsis
218
252
  # privmsg(:greet, "#some_channel", :from => @butler.myself.nick)
@@ -230,7 +264,7 @@ class Butler
230
264
 
231
265
  # == About
232
266
  # Sends a notice (localized and formatted) to any number of recipients
233
- # (Users or Channels)
267
+ # (UserList or ChannelList)
234
268
  #
235
269
  # == Synopsis
236
270
  # notice(:greet, "#some_channel", :from => @butler.myself.nick)