butler 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. data/CHANGELOG +4 -0
  2. data/GPL.txt +340 -0
  3. data/LICENSE.txt +52 -0
  4. data/README +37 -0
  5. data/Rakefile +334 -0
  6. data/bin/botcontrol +230 -0
  7. data/data/butler/config_template.yaml +4 -0
  8. data/data/butler/dialogs/backup.rb +19 -0
  9. data/data/butler/dialogs/botcontrol.rb +4 -0
  10. data/data/butler/dialogs/config.rb +1 -0
  11. data/data/butler/dialogs/create.rb +53 -0
  12. data/data/butler/dialogs/delete.rb +3 -0
  13. data/data/butler/dialogs/en/backup.yaml +6 -0
  14. data/data/butler/dialogs/en/botcontrol.yaml +5 -0
  15. data/data/butler/dialogs/en/create.yaml +11 -0
  16. data/data/butler/dialogs/en/delete.yaml +2 -0
  17. data/data/butler/dialogs/en/help.yaml +17 -0
  18. data/data/butler/dialogs/en/info.yaml +13 -0
  19. data/data/butler/dialogs/en/list.yaml +4 -0
  20. data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
  21. data/data/butler/dialogs/en/rename.yaml +3 -0
  22. data/data/butler/dialogs/en/start.yaml +3 -0
  23. data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
  24. data/data/butler/dialogs/en/uninstall.yaml +5 -0
  25. data/data/butler/dialogs/en/unknown_command.yaml +2 -0
  26. data/data/butler/dialogs/help.rb +11 -0
  27. data/data/butler/dialogs/info.rb +27 -0
  28. data/data/butler/dialogs/interactive.rb +1 -0
  29. data/data/butler/dialogs/list.rb +10 -0
  30. data/data/butler/dialogs/notyetimplemented.rb +1 -0
  31. data/data/butler/dialogs/rename.rb +4 -0
  32. data/data/butler/dialogs/selectbot.rb +2 -0
  33. data/data/butler/dialogs/start.rb +5 -0
  34. data/data/butler/dialogs/sync_plugins.rb +30 -0
  35. data/data/butler/dialogs/uninstall.rb +17 -0
  36. data/data/butler/dialogs/unknown_command.rb +1 -0
  37. data/data/butler/plugins/core/logout.rb +41 -0
  38. data/data/butler/plugins/core/plugins.rb +134 -0
  39. data/data/butler/plugins/core/privilege.rb +103 -0
  40. data/data/butler/plugins/core/user.rb +166 -0
  41. data/data/butler/plugins/dev/eval.rb +64 -0
  42. data/data/butler/plugins/dev/nometa.rb +14 -0
  43. data/data/butler/plugins/dev/onhandlers.rb +93 -0
  44. data/data/butler/plugins/dev/raw.rb +36 -0
  45. data/data/butler/plugins/dev/rawlog.rb +77 -0
  46. data/data/butler/plugins/games/eightball.rb +54 -0
  47. data/data/butler/plugins/games/mastermind.rb +174 -0
  48. data/data/butler/plugins/irc/action.rb +36 -0
  49. data/data/butler/plugins/irc/join.rb +38 -0
  50. data/data/butler/plugins/irc/notice.rb +36 -0
  51. data/data/butler/plugins/irc/part.rb +38 -0
  52. data/data/butler/plugins/irc/privmsg.rb +36 -0
  53. data/data/butler/plugins/irc/quit.rb +36 -0
  54. data/data/butler/plugins/operator/deop.rb +41 -0
  55. data/data/butler/plugins/operator/devoice.rb +41 -0
  56. data/data/butler/plugins/operator/limit.rb +47 -0
  57. data/data/butler/plugins/operator/op.rb +41 -0
  58. data/data/butler/plugins/operator/voice.rb +41 -0
  59. data/data/butler/plugins/public/help.rb +69 -0
  60. data/data/butler/plugins/public/login.rb +72 -0
  61. data/data/butler/plugins/public/usage.rb +49 -0
  62. data/data/butler/plugins/service/clones.rb +56 -0
  63. data/data/butler/plugins/service/define.rb +47 -0
  64. data/data/butler/plugins/service/log.rb +183 -0
  65. data/data/butler/plugins/service/svn.rb +91 -0
  66. data/data/butler/plugins/util/cycle.rb +98 -0
  67. data/data/butler/plugins/util/load.rb +41 -0
  68. data/data/butler/plugins/util/pong.rb +29 -0
  69. data/data/butler/strings/random/acknowledge.en.yaml +5 -0
  70. data/data/butler/strings/random/gratitude.en.yaml +3 -0
  71. data/data/butler/strings/random/hello.en.yaml +4 -0
  72. data/data/butler/strings/random/ignorance.en.yaml +7 -0
  73. data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
  74. data/data/butler/strings/random/insult.en.yaml +3 -0
  75. data/data/butler/strings/random/rejection.en.yaml +12 -0
  76. data/data/man/botcontrol.1 +17 -0
  77. data/lib/access.rb +187 -0
  78. data/lib/access/admin.rb +16 -0
  79. data/lib/access/privilege.rb +122 -0
  80. data/lib/access/role.rb +102 -0
  81. data/lib/access/savable.rb +18 -0
  82. data/lib/access/user.rb +180 -0
  83. data/lib/access/yamlbase.rb +126 -0
  84. data/lib/butler.rb +188 -0
  85. data/lib/butler/bot.rb +247 -0
  86. data/lib/butler/control.rb +93 -0
  87. data/lib/butler/dialog.rb +64 -0
  88. data/lib/butler/initialvalues.rb +40 -0
  89. data/lib/butler/irc/channel.rb +135 -0
  90. data/lib/butler/irc/channels.rb +96 -0
  91. data/lib/butler/irc/client.rb +351 -0
  92. data/lib/butler/irc/hostmask.rb +53 -0
  93. data/lib/butler/irc/message.rb +184 -0
  94. data/lib/butler/irc/parser.rb +125 -0
  95. data/lib/butler/irc/parser/commands.rb +83 -0
  96. data/lib/butler/irc/parser/generic.rb +343 -0
  97. data/lib/butler/irc/socket.rb +378 -0
  98. data/lib/butler/irc/string.rb +186 -0
  99. data/lib/butler/irc/topic.rb +15 -0
  100. data/lib/butler/irc/user.rb +265 -0
  101. data/lib/butler/irc/users.rb +112 -0
  102. data/lib/butler/plugin.rb +249 -0
  103. data/lib/butler/plugin/configproxy.rb +35 -0
  104. data/lib/butler/plugin/mapper.rb +85 -0
  105. data/lib/butler/plugin/matcher.rb +55 -0
  106. data/lib/butler/plugin/onhandlers.rb +70 -0
  107. data/lib/butler/plugin/trigger.rb +58 -0
  108. data/lib/butler/plugins.rb +147 -0
  109. data/lib/butler/version.rb +17 -0
  110. data/lib/cloptions.rb +217 -0
  111. data/lib/cloptions/adapters.rb +24 -0
  112. data/lib/cloptions/switch.rb +132 -0
  113. data/lib/configuration.rb +223 -0
  114. data/lib/dialogline.rb +296 -0
  115. data/lib/dialogline/localizations.rb +24 -0
  116. data/lib/durations.rb +57 -0
  117. data/lib/event.rb +295 -0
  118. data/lib/event/at.rb +64 -0
  119. data/lib/event/every.rb +56 -0
  120. data/lib/event/timed.rb +112 -0
  121. data/lib/installer.rb +75 -0
  122. data/lib/iterator.rb +34 -0
  123. data/lib/log.rb +68 -0
  124. data/lib/log/comfort.rb +85 -0
  125. data/lib/log/converter.rb +23 -0
  126. data/lib/log/entry.rb +152 -0
  127. data/lib/log/fakeio.rb +55 -0
  128. data/lib/log/file.rb +54 -0
  129. data/lib/log/filereader.rb +81 -0
  130. data/lib/log/forward.rb +49 -0
  131. data/lib/log/methods.rb +39 -0
  132. data/lib/log/nolog.rb +18 -0
  133. data/lib/log/splitter.rb +26 -0
  134. data/lib/ostructfixed.rb +26 -0
  135. data/lib/ruby/array/columnize.rb +38 -0
  136. data/lib/ruby/dir/mktree.rb +28 -0
  137. data/lib/ruby/enumerable/join.rb +13 -0
  138. data/lib/ruby/exception/detailed.rb +24 -0
  139. data/lib/ruby/file/append.rb +11 -0
  140. data/lib/ruby/file/write.rb +11 -0
  141. data/lib/ruby/hash/zip.rb +15 -0
  142. data/lib/ruby/kernel/bench.rb +15 -0
  143. data/lib/ruby/kernel/daemonize.rb +42 -0
  144. data/lib/ruby/kernel/non_verbose.rb +17 -0
  145. data/lib/ruby/kernel/safe_fork.rb +18 -0
  146. data/lib/ruby/range/stepped.rb +11 -0
  147. data/lib/ruby/string/arguments.rb +72 -0
  148. data/lib/ruby/string/chunks.rb +15 -0
  149. data/lib/ruby/string/post_arguments.rb +44 -0
  150. data/lib/ruby/string/unescaped.rb +17 -0
  151. data/lib/scheduler.rb +164 -0
  152. data/lib/scriptfile.rb +101 -0
  153. data/lib/templater.rb +86 -0
  154. data/test/cloptions.rb +134 -0
  155. data/test/cv.rb +28 -0
  156. data/test/irc/client.rb +85 -0
  157. data/test/irc/client_login.txt +53 -0
  158. data/test/irc/client_subscribe.txt +8 -0
  159. data/test/irc/message.rb +30 -0
  160. data/test/irc/messages.txt +64 -0
  161. data/test/irc/parser.rb +13 -0
  162. data/test/irc/profile_parser.rb +12 -0
  163. data/test/irc/users.rb +28 -0
  164. metadata +256 -0
@@ -0,0 +1,15 @@
1
+ class Butler
2
+ module IRC
3
+ Topic = Struct.new(:text, :set_by, :set_at) unless defined? Topic
4
+ class Topic
5
+ def inspect
6
+ "#<%s %s --%s on %s>" % [
7
+ self.class,
8
+ text,
9
+ set_by,
10
+ set_at.strftime("%Y-%m-%d %H:%M")
11
+ ]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,265 @@
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
+ # Enumerable: channels (IRC::Channel instances) the user is known to be in
12
+ class User
13
+ module Flags
14
+ ADMIN = 1
15
+ IRCOP = 2
16
+ AOP = 4
17
+ OP = 8
18
+ VOICE = 16
19
+ UOP = 32
20
+ end
21
+ include Flags
22
+
23
+ USER_STATUS = [:unknown, :myself, :online, :out_of_sight, :offline, :mutated]
24
+
25
+ PREFIXES = Hash.new(0)
26
+ PREFIXES["@"] = OP
27
+ PREFIXES["+"] = VOICE
28
+ PREFIXES["-"] = UOP
29
+
30
+ include Comparable
31
+ include Enumerable
32
+
33
+ #nickname of a user
34
+ attr_reader :nick
35
+
36
+ #username of a user
37
+ attr_reader :user
38
+
39
+ #hostpart of a user
40
+ attr_reader :host
41
+
42
+ #realname of a user
43
+ attr_reader :real
44
+
45
+ #users last known status (:unknown, :online, :out_of_sight, :offline)
46
+ attr_reader :status
47
+
48
+ #lowercase nickname
49
+ attr_reader :compare
50
+
51
+ def initialize(users, channels, nick=nil, user=nil, host=nil, real=nil)
52
+ @nick, @user, @host, @real = nil
53
+ @users = users
54
+ @nick = nick.to_str.strip_user_prefixes.freeze if nick
55
+ @user = user.freeze if user
56
+ @host = host.freeze if host
57
+ @real = real.freeze if real
58
+
59
+ @compare = @nick ? @nick.downcase : "\xff"
60
+ @all_channels = channels
61
+ @channels = {} # downcased channelname => flags
62
+ @status = :unknown
63
+ @is_away = false
64
+ @away_message = ""
65
+ end
66
+
67
+ # Iterate over all users in this channel
68
+ def each
69
+ @channels.each_value { |channel| yield @all_channels[channel] }
70
+ end
71
+
72
+ def channels
73
+ @all_channels.map_names(*@channels.keys)
74
+ end
75
+
76
+ def channel_names
77
+ @channels.keys
78
+ end
79
+
80
+ # update userdata (set user, host, real if they are nil)
81
+ def update(user=nil, host=nil, real=nil) #:nodoc:
82
+ raise ArgumentError, "Non assignable new value for user" if @user and user and @user != user
83
+ raise ArgumentError, "Non assignable new value for host" if @host and host and @host != host
84
+ raise ArgumentError, "Non assignable new value for real" if @real and real and @real != real
85
+ @user = user.freeze if user
86
+ @host = host.freeze if host
87
+ @real = real.freeze if real
88
+ self
89
+ end
90
+
91
+ # set users nickname (should only be used by Butler::IRC::Parser)
92
+ def nick=(nick) #:nodoc:
93
+ nick = nick.to_str.strip_user_prefixes
94
+ raise ArgumentError, "Invalid nickname #{nick}" unless nick.valid_nickname?
95
+ @users.lock.synchronize {
96
+ old_index = @compare
97
+ @nick = nick
98
+ @compare = @nick.downcase
99
+ @users.rehash(old_index, @compare)
100
+ }
101
+ end
102
+
103
+ # set user-status (should only be used by Butler::IRC::Parser)
104
+ def status=(status) #:nodoc:
105
+ raise ArgumentError, "Invalid status #{status}" unless USER_STATUS.include?(status)
106
+ @status = status unless @status == :myself
107
+ end
108
+
109
+ # set away status (should only be used by Butler::IRC::Parser)
110
+ def away=(is_away) #:nodoc:
111
+ @is_away = is_away
112
+ end
113
+
114
+ # check if user has op (+o) in given channel (String or Butler::IRC::Channel)
115
+ def op?(in_channel)
116
+ !(@channels[in_channel.to_str.downcase] & OP).zero?
117
+ end
118
+
119
+ # check if user has voice (+v) in given channel (String or Butler::IRC::Channel)
120
+ def voice?(in_channel)
121
+ !(@channels[in_channel.to_str.downcase] & VOICE).zero?
122
+ end
123
+
124
+ # check if user has uop (+u) in given channel (String or Butler::IRC::Channel)
125
+ def uop?(in_channel)
126
+ !(@channels[in_channel.to_str.downcase] & UOP).zero?
127
+ end
128
+
129
+ def in_channel?(channel)
130
+ @channels.has_key?(channel.to_str.downcase)
131
+ end
132
+
133
+ # check if user is away
134
+ def is_away?
135
+ @is_away
136
+ end
137
+
138
+ # retrieve away message
139
+ def away_message
140
+ @away_message
141
+ end
142
+
143
+ # set away message (should only be used by Butler::IRC::Parser)
144
+ def away_message=(message) #:nodoc:
145
+ @away_message = message.freeze
146
+ end
147
+
148
+ def common_channels(with_other_user)
149
+ @channels.keys & with_other_user.channel_names
150
+ end
151
+
152
+ def common_channels?(with_other_user)
153
+ !common_channels(with_other_user).empty?
154
+ end
155
+
156
+ # FIXME
157
+ # add a channel to the user (should only be used by Butler::IRC::Parser)
158
+ def add_channel(channel, reason)
159
+ @channels[channel.to_str.downcase] ||= 0
160
+ if @status != :myself && [:out_of_sight, :unknown, :offline].include?(@status) && common_channels?(@users.myself) then
161
+ self.status = :online
162
+ end
163
+ self
164
+ end
165
+
166
+ # FIXME
167
+ # remove a channel from the user (should only be used by Butler::IRC::Parser)
168
+ def delete_channel(channel, reason)
169
+ @channels.delete(channel.to_str.downcase)
170
+ if @channels.empty? && ![:out_of_sight, :unknown, :myself].include?(@status) then
171
+ self.status = :out_of_sight
172
+ end
173
+ end
174
+
175
+ # FIXME
176
+ def add_flags(channel, flags)
177
+ channel = channel.to_str.downcase
178
+ raise ArgumentError, "User #{self} is not listed in #{channel}" unless @channels.include?(channel)
179
+ @channels[channel] |= flags
180
+ end
181
+
182
+ # FIXME
183
+ def delete_flags(channel, flags)
184
+ channel = channel.to_str.downcase
185
+ raise ArgumentError, "User #{self} is not listed in #{channel}" unless @channels.include?(channel)
186
+ @channels[channel] &= ~flags
187
+ end
188
+
189
+ # FIXME
190
+ def quit
191
+ @channels.each { |channel, _| delete_channel(channel, :quit) }
192
+ self.status = :offline
193
+ @users.delete(self, :quit)
194
+ end
195
+
196
+ # FIXME
197
+ def kill
198
+ @channels.each { |channel, _| delete_channel(channel, :kill) }
199
+ self.status = :offline
200
+ @users.delete(self, :kill)
201
+ end
202
+
203
+ # Returns the (frozen!) nickname of the user
204
+ def to_s
205
+ @nick
206
+ end
207
+
208
+ # :nodoc: string representation of Butler::IRC::User (nickname)
209
+ def to_str
210
+ @compare
211
+ end
212
+
213
+ # same Butler::IRC::User?
214
+ # FIXME, might require stricter comparison for IRC::User objects
215
+ def ==(other)
216
+ if other.kind_of?(User) then
217
+ (
218
+ (!(@nick && other.nick) || @compare == other.compare) &&
219
+ (!(@user && other.user) || @user == other.user) &&
220
+ (!(@host && other.host) || @host == other.host) &&
221
+ (!(@real && other.real) || @real == other.real)
222
+ )
223
+ else
224
+ @compare == other.to_str.downcase
225
+ end
226
+ end
227
+
228
+ # Matching nickname to a regular expression.
229
+ # Same as User#to_s =~ /regular expression/
230
+ def =~(regex)
231
+ @nick =~ regex
232
+ end
233
+
234
+ # Lowercase String-comparison of nicknames.
235
+ # accepts everything that responds to :to_str
236
+ def ===(other)
237
+ @nick && @compare === other
238
+ end
239
+
240
+ # Compares nicknames
241
+ def <=>(other)
242
+ @compare <=> other.to_str.downcase
243
+ end
244
+
245
+ def inspect
246
+ "#<%s:0x%x %s!%s@%s (%s)>" % [self.class, object_id, @nick || "?", @user || "?", @host || "?", @real || "?"]
247
+ end
248
+
249
+ undef_method(:dup)
250
+ undef_method(:clone)
251
+ end # User
252
+ end
253
+ end
254
+
255
+ class Fixnum
256
+ # Return a String containing the user-prefixes for a flagset, e.g.
257
+ # (Flags::OP | Flags::Voice).to_user_prefixes # => "@-"
258
+ def to_user_prefixes
259
+ result = ""
260
+ Butler::IRC::User::PREFIXES.each { |char, value|
261
+ result += char unless (self & value).zero?
262
+ }
263
+ return result
264
+ end
265
+ end
@@ -0,0 +1,112 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'butler/irc/channels'
10
+ require 'butler/irc/string'
11
+ require 'butler/irc/user'
12
+ require 'thread'
13
+
14
+
15
+
16
+ class Butler
17
+ module IRC
18
+ # Enumerable: all known & visible users
19
+ class Users
20
+ include Enumerable
21
+
22
+ # the user that represents the clients user
23
+ attr_reader :myself
24
+ # provides a global (within Butler::IRC::User) mutex
25
+ attr_reader :lock
26
+
27
+ def initialize(client)
28
+ @client = client
29
+ @users = {}
30
+ @lost = {}
31
+ @lock = Mutex.new
32
+ @myself = nil
33
+ @channels = nil
34
+ end
35
+
36
+ def channels=(channels)
37
+ raise "Can't set @channels twice" if @channels
38
+ @channels = channels
39
+ end
40
+
41
+ def map_nicks(*nicks)
42
+ @users.values_at(*nicks)
43
+ end
44
+
45
+ def create_myself(nick, user, real)
46
+ @myself = create(nick, nil, nil, nil)
47
+ @myself.status = :myself
48
+ end
49
+
50
+ def create(nick, *args)
51
+ @lock.synchronize {
52
+ new_user = User.new(self, @channels, nick, *args)
53
+ old_user = self[nick]
54
+ if old_user == new_user then
55
+ old_user.update(*args)
56
+ old_user
57
+
58
+ elsif !old_user then
59
+ @users[new_user.compare] = new_user
60
+
61
+ # if old user was out of sight the new user overrides the old
62
+ elsif old_user.status == :out_of_sight || old_user.status == :unknown then
63
+ @users.delete(old_user.compare)
64
+ @users[new_user.compare] = new_user
65
+
66
+ else # Should NOT happen
67
+ old_user.status = :mutated
68
+ @users.delete(old_user.compare)
69
+ @users[new_user.compare] = new_user
70
+ raise "A user mutated #{old_user.inspect} -> #{new_user.inspect}"
71
+ end
72
+ }
73
+ end
74
+
75
+ # list clones (comparing all known users)
76
+ def clones(strong=false, min=2)
77
+ sieve = Hash.new { |h,k| h[k] = [] }
78
+ if strong then
79
+ each { |user|
80
+ sieve["#{user.user}@#{user.host}"] << user if (user.host && user.user)
81
+ }
82
+ else
83
+ each { |user|
84
+ sieve[user.host] << user if user.host
85
+ }
86
+ end
87
+ sieve.reject { |host, users| users.length < min }
88
+ end
89
+
90
+ def each(&block)
91
+ @users.each_value(&block)
92
+ end
93
+
94
+ def rehash(old_index, new_index)
95
+ @users[new_index] = @users.delete(old_index)
96
+ end
97
+
98
+ def delete(user, reason)
99
+ @users.delete(user.to_str.downcase)
100
+ end
101
+
102
+ def delete_channel(channel, reason)
103
+ @users.each_value { |user| user.delete_channel(channel, reason) }
104
+ end
105
+
106
+ # get a user by nick (may include user prefixes)
107
+ def [](nick)
108
+ @users[nick.to_str.strip_user_prefixes.downcase]
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,249 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'butler/plugin/configproxy'
10
+ require 'butler/plugin/onhandlers'
11
+ require 'butler/plugin/mapper'
12
+ require 'butler/plugin/matcher'
13
+ require 'butler/plugin/trigger'
14
+ require 'log/comfort'
15
+ require 'ostructfixed'
16
+ require 'ruby/exception/detailed'
17
+ require 'scriptfile'
18
+ require 'templater'
19
+
20
+
21
+
22
+ class Butler
23
+ class Plugin
24
+ class <<self
25
+ attr_reader :about
26
+ attr_reader :base
27
+ attr_reader :name
28
+ attr_reader :butler
29
+ attr_reader :config
30
+ attr_reader :help
31
+ attr_reader :path
32
+ attr_reader :strings
33
+ attr_reader :summary
34
+ attr_reader :usage
35
+
36
+ # this method is called to initialize the plugin-class,
37
+ # do not override
38
+ 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}")
45
+
46
+ if File.directory?(path) then
47
+ raise "Not supported yet" # FIXME
48
+ @path = OpenStruct.new(
49
+ :data => (@path+"/data").freeze,
50
+ :strings => (@path+"/strings").freeze,
51
+ :requires => (@path+"/requires").freeze
52
+ )
53
+ else
54
+ data = YAML.load(ScriptFile.read(path)) || {}
55
+ @strings = data[:strings] || {}
56
+ @about = data[:about] || {}
57
+ @help = data[:help] || {}
58
+ mappers = data[:map] || {}
59
+ @summary = data[:summary] || {}
60
+ triggers = data[:trigger] || nil
61
+ @usage = data[:usage] || {}
62
+ @path = OpenStruct.new(
63
+ :data => nil,
64
+ :strings => nil,
65
+ :requires => nil
66
+ )
67
+ end
68
+
69
+ {:usage=>[@usage],:strings=>@strings.values,:help=>@help.values}.each { |key,tmpls|
70
+ tmpls.each { |tmpl|
71
+ create_templates(tmpl, key)
72
+ }
73
+ }
74
+
75
+ mappers.each { |meth, expressions|
76
+ map(meth, expressions)
77
+ }
78
+ trigger(triggers) if triggers
79
+
80
+ info("Loaded plugin '#{@base}' (#{self})")
81
+ end
82
+
83
+ def create_templates(tmpl, name)
84
+ tmpl.each { |key,value|
85
+ begin
86
+ tmpl[key] = Templater.new(value)
87
+ rescue Exception => e
88
+ e.extend Exception::Detail
89
+ e.prepend "Could not map #{key} in #{@base} (#{name})."
90
+ exception(e)
91
+ end
92
+ }
93
+ end
94
+
95
+ # initialize a configuration
96
+ def configuration(settings)
97
+ settings.each { |key, value|
98
+ @config[key] = value unless @config.has_key?(key)
99
+ }
100
+ end
101
+
102
+ # Will create an instance of the plugin and call usage on it
103
+ def plugin_usage(message, data={})
104
+ new(message).usage(data)
105
+ end
106
+
107
+ def on_load
108
+ end
109
+
110
+ def trigger(commands)
111
+ commands = { "en" => commands } unless commands.kind_of?(Hash)
112
+ commands.each { |lang, command|
113
+ trigger = Trigger.new(self, lang, command)
114
+ @butler.add_command(trigger)
115
+ @commands << trigger
116
+ }
117
+ end
118
+
119
+ def map(meth, expressions)
120
+ expressions = { "en" => expressions } unless expressions.kind_of?(Hash)
121
+ expressions.each { |lang, expression|
122
+ mapper = Mapper.new(self, meth, lang, expression)
123
+ @butler.add_command(mapper)
124
+ @commands << mapper
125
+ }
126
+ end
127
+
128
+ def every(*args, &block)
129
+ @butler.scheduler.every(*args, &block)
130
+ end
131
+
132
+ def at(*args, &block)
133
+ @butler.scheduler.at(*args, &block)
134
+ end
135
+
136
+ def timed(*args, &block)
137
+ @butler.scheduler.timed(*args, &block)
138
+ end
139
+
140
+ def subscribe(*args, &block)
141
+ listener = @butler.subscribe(*args, &block)
142
+ @listener << listener
143
+ listener
144
+ end
145
+
146
+ def on_unload
147
+ end
148
+
149
+ def unload_plugin
150
+ info("Unloading plugin '#{@base}' (#{self})")
151
+ @commands.each { |command| @butler.delete_command(command) }
152
+ @listener.each { |listener| listener.unsubscribe }
153
+ end
154
+
155
+ end
156
+
157
+ include Log::Comfort
158
+ extend Log::Comfort
159
+
160
+ attr_reader :butler
161
+ attr_reader :message
162
+
163
+ def initialize(message)
164
+ @butler = plugin.butler
165
+ @message = message
166
+ end
167
+
168
+ alias plugin class
169
+
170
+ # gets the arguments and downcases if possible
171
+ def arguments(*params)
172
+ args = @message.arguments[*params]
173
+ if args.kind_of?(Array) then
174
+ args.map { |arg| arg.downcase }
175
+ elsif args then
176
+ args.downcase
177
+ else
178
+ nil
179
+ end
180
+ end
181
+
182
+ def localize(index, data={})
183
+ string = plugin.strings[index]
184
+ string &&= (string[@message.language] || string["en"])
185
+ return "Unknown String '#{index}'" unless string
186
+ string.result(self, data)
187
+ end
188
+ alias _ localize
189
+
190
+ def usage(data={})
191
+ string = plugin.usage
192
+ if string = string[@message.language] || string["en"] then
193
+ ("Usage '#{plugin.name}': #{@message.invocation}"+string.result(self, data)).mirc_formatted
194
+ else
195
+ "No usage provided"
196
+ end
197
+ end
198
+
199
+ def unknown(n=-1)
200
+ "Unknown argument #{@message.arguments[n]} for #{@message.arguments[0...n].join(' ')}"
201
+ end
202
+
203
+ # Only works if @message is set and conditions of Messag#answer are met.
204
+ # If string is a Symbol it considers it a key for localization, looks it up
205
+ # and translates it using vars as variables for the string interpolation.
206
+ # The string is subsequently mirc_formatted (see String#mirc_formatted).
207
+ # 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)
211
+ end
212
+
213
+ # == About
214
+ # Sends a privmsg (localized and formatted) to any number of recipients
215
+ # (Users or Channels)
216
+ #
217
+ # == Synopsis
218
+ # privmsg(:greet, "#some_channel", :from => @butler.myself.nick)
219
+ #
220
+ # == Description
221
+ # If string is a Symbol it considers it a key for localization, looks it up
222
+ # and translates it using the last argument as variables for the string
223
+ # interpolation, iff the last argument is a Hash.
224
+ # The string is subsequently mirc_formatted (see String#mirc_formatted).
225
+ def privmsg(string, *args)
226
+ vars = args.last.kind_of?(Hash) ? args.pop : {}
227
+ string = localize(string, vars) if string.kind_of?(Symbol)
228
+ @butler.privmsg(string.mirc_formatted, *args)
229
+ end
230
+
231
+ # == About
232
+ # Sends a notice (localized and formatted) to any number of recipients
233
+ # (Users or Channels)
234
+ #
235
+ # == Synopsis
236
+ # notice(:greet, "#some_channel", :from => @butler.myself.nick)
237
+ #
238
+ # == Description
239
+ # If string is a Symbol it considers it a key for localization, looks it up
240
+ # and translates it using the last argument as variables for the string
241
+ # interpolation, iff the last argument is a Hash.
242
+ # The string is subsequently mirc_formatted (see String#mirc_formatted).
243
+ def notice(string, *args)
244
+ vars = args.last.kind_of?(Hash) ? args.pop : {}
245
+ string = localize(string, vars) if string.kind_of?(Symbol)
246
+ @butler.notice(string.mirc_formatted, *args)
247
+ end
248
+ end
249
+ end