butler 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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