butler 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +4 -0
- data/GPL.txt +340 -0
- data/LICENSE.txt +52 -0
- data/README +37 -0
- data/Rakefile +334 -0
- data/bin/botcontrol +230 -0
- data/data/butler/config_template.yaml +4 -0
- data/data/butler/dialogs/backup.rb +19 -0
- data/data/butler/dialogs/botcontrol.rb +4 -0
- data/data/butler/dialogs/config.rb +1 -0
- data/data/butler/dialogs/create.rb +53 -0
- data/data/butler/dialogs/delete.rb +3 -0
- data/data/butler/dialogs/en/backup.yaml +6 -0
- data/data/butler/dialogs/en/botcontrol.yaml +5 -0
- data/data/butler/dialogs/en/create.yaml +11 -0
- data/data/butler/dialogs/en/delete.yaml +2 -0
- data/data/butler/dialogs/en/help.yaml +17 -0
- data/data/butler/dialogs/en/info.yaml +13 -0
- data/data/butler/dialogs/en/list.yaml +4 -0
- data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
- data/data/butler/dialogs/en/rename.yaml +3 -0
- data/data/butler/dialogs/en/start.yaml +3 -0
- data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
- data/data/butler/dialogs/en/uninstall.yaml +5 -0
- data/data/butler/dialogs/en/unknown_command.yaml +2 -0
- data/data/butler/dialogs/help.rb +11 -0
- data/data/butler/dialogs/info.rb +27 -0
- data/data/butler/dialogs/interactive.rb +1 -0
- data/data/butler/dialogs/list.rb +10 -0
- data/data/butler/dialogs/notyetimplemented.rb +1 -0
- data/data/butler/dialogs/rename.rb +4 -0
- data/data/butler/dialogs/selectbot.rb +2 -0
- data/data/butler/dialogs/start.rb +5 -0
- data/data/butler/dialogs/sync_plugins.rb +30 -0
- data/data/butler/dialogs/uninstall.rb +17 -0
- data/data/butler/dialogs/unknown_command.rb +1 -0
- data/data/butler/plugins/core/logout.rb +41 -0
- data/data/butler/plugins/core/plugins.rb +134 -0
- data/data/butler/plugins/core/privilege.rb +103 -0
- data/data/butler/plugins/core/user.rb +166 -0
- data/data/butler/plugins/dev/eval.rb +64 -0
- data/data/butler/plugins/dev/nometa.rb +14 -0
- data/data/butler/plugins/dev/onhandlers.rb +93 -0
- data/data/butler/plugins/dev/raw.rb +36 -0
- data/data/butler/plugins/dev/rawlog.rb +77 -0
- data/data/butler/plugins/games/eightball.rb +54 -0
- data/data/butler/plugins/games/mastermind.rb +174 -0
- data/data/butler/plugins/irc/action.rb +36 -0
- data/data/butler/plugins/irc/join.rb +38 -0
- data/data/butler/plugins/irc/notice.rb +36 -0
- data/data/butler/plugins/irc/part.rb +38 -0
- data/data/butler/plugins/irc/privmsg.rb +36 -0
- data/data/butler/plugins/irc/quit.rb +36 -0
- data/data/butler/plugins/operator/deop.rb +41 -0
- data/data/butler/plugins/operator/devoice.rb +41 -0
- data/data/butler/plugins/operator/limit.rb +47 -0
- data/data/butler/plugins/operator/op.rb +41 -0
- data/data/butler/plugins/operator/voice.rb +41 -0
- data/data/butler/plugins/public/help.rb +69 -0
- data/data/butler/plugins/public/login.rb +72 -0
- data/data/butler/plugins/public/usage.rb +49 -0
- data/data/butler/plugins/service/clones.rb +56 -0
- data/data/butler/plugins/service/define.rb +47 -0
- data/data/butler/plugins/service/log.rb +183 -0
- data/data/butler/plugins/service/svn.rb +91 -0
- data/data/butler/plugins/util/cycle.rb +98 -0
- data/data/butler/plugins/util/load.rb +41 -0
- data/data/butler/plugins/util/pong.rb +29 -0
- data/data/butler/strings/random/acknowledge.en.yaml +5 -0
- data/data/butler/strings/random/gratitude.en.yaml +3 -0
- data/data/butler/strings/random/hello.en.yaml +4 -0
- data/data/butler/strings/random/ignorance.en.yaml +7 -0
- data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
- data/data/butler/strings/random/insult.en.yaml +3 -0
- data/data/butler/strings/random/rejection.en.yaml +12 -0
- data/data/man/botcontrol.1 +17 -0
- data/lib/access.rb +187 -0
- data/lib/access/admin.rb +16 -0
- data/lib/access/privilege.rb +122 -0
- data/lib/access/role.rb +102 -0
- data/lib/access/savable.rb +18 -0
- data/lib/access/user.rb +180 -0
- data/lib/access/yamlbase.rb +126 -0
- data/lib/butler.rb +188 -0
- data/lib/butler/bot.rb +247 -0
- data/lib/butler/control.rb +93 -0
- data/lib/butler/dialog.rb +64 -0
- data/lib/butler/initialvalues.rb +40 -0
- data/lib/butler/irc/channel.rb +135 -0
- data/lib/butler/irc/channels.rb +96 -0
- data/lib/butler/irc/client.rb +351 -0
- data/lib/butler/irc/hostmask.rb +53 -0
- data/lib/butler/irc/message.rb +184 -0
- data/lib/butler/irc/parser.rb +125 -0
- data/lib/butler/irc/parser/commands.rb +83 -0
- data/lib/butler/irc/parser/generic.rb +343 -0
- data/lib/butler/irc/socket.rb +378 -0
- data/lib/butler/irc/string.rb +186 -0
- data/lib/butler/irc/topic.rb +15 -0
- data/lib/butler/irc/user.rb +265 -0
- data/lib/butler/irc/users.rb +112 -0
- data/lib/butler/plugin.rb +249 -0
- data/lib/butler/plugin/configproxy.rb +35 -0
- data/lib/butler/plugin/mapper.rb +85 -0
- data/lib/butler/plugin/matcher.rb +55 -0
- data/lib/butler/plugin/onhandlers.rb +70 -0
- data/lib/butler/plugin/trigger.rb +58 -0
- data/lib/butler/plugins.rb +147 -0
- data/lib/butler/version.rb +17 -0
- data/lib/cloptions.rb +217 -0
- data/lib/cloptions/adapters.rb +24 -0
- data/lib/cloptions/switch.rb +132 -0
- data/lib/configuration.rb +223 -0
- data/lib/dialogline.rb +296 -0
- data/lib/dialogline/localizations.rb +24 -0
- data/lib/durations.rb +57 -0
- data/lib/event.rb +295 -0
- data/lib/event/at.rb +64 -0
- data/lib/event/every.rb +56 -0
- data/lib/event/timed.rb +112 -0
- data/lib/installer.rb +75 -0
- data/lib/iterator.rb +34 -0
- data/lib/log.rb +68 -0
- data/lib/log/comfort.rb +85 -0
- data/lib/log/converter.rb +23 -0
- data/lib/log/entry.rb +152 -0
- data/lib/log/fakeio.rb +55 -0
- data/lib/log/file.rb +54 -0
- data/lib/log/filereader.rb +81 -0
- data/lib/log/forward.rb +49 -0
- data/lib/log/methods.rb +39 -0
- data/lib/log/nolog.rb +18 -0
- data/lib/log/splitter.rb +26 -0
- data/lib/ostructfixed.rb +26 -0
- data/lib/ruby/array/columnize.rb +38 -0
- data/lib/ruby/dir/mktree.rb +28 -0
- data/lib/ruby/enumerable/join.rb +13 -0
- data/lib/ruby/exception/detailed.rb +24 -0
- data/lib/ruby/file/append.rb +11 -0
- data/lib/ruby/file/write.rb +11 -0
- data/lib/ruby/hash/zip.rb +15 -0
- data/lib/ruby/kernel/bench.rb +15 -0
- data/lib/ruby/kernel/daemonize.rb +42 -0
- data/lib/ruby/kernel/non_verbose.rb +17 -0
- data/lib/ruby/kernel/safe_fork.rb +18 -0
- data/lib/ruby/range/stepped.rb +11 -0
- data/lib/ruby/string/arguments.rb +72 -0
- data/lib/ruby/string/chunks.rb +15 -0
- data/lib/ruby/string/post_arguments.rb +44 -0
- data/lib/ruby/string/unescaped.rb +17 -0
- data/lib/scheduler.rb +164 -0
- data/lib/scriptfile.rb +101 -0
- data/lib/templater.rb +86 -0
- data/test/cloptions.rb +134 -0
- data/test/cv.rb +28 -0
- data/test/irc/client.rb +85 -0
- data/test/irc/client_login.txt +53 -0
- data/test/irc/client_subscribe.txt +8 -0
- data/test/irc/message.rb +30 -0
- data/test/irc/messages.txt +64 -0
- data/test/irc/parser.rb +13 -0
- data/test/irc/profile_parser.rb +12 -0
- data/test/irc/users.rb +28 -0
- 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
|