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.
- 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
data/lib/butler/bot.rb
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# See LICENSE.txt for permissions.
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
require 'butler'
|
|
10
|
+
require 'butler/irc/client'
|
|
11
|
+
require 'configuration'
|
|
12
|
+
require 'log'
|
|
13
|
+
require 'butler/plugins'
|
|
14
|
+
require 'ruby/string/arguments'
|
|
15
|
+
require 'ruby/string/post_arguments'
|
|
16
|
+
require 'scheduler'
|
|
17
|
+
require 'set'
|
|
18
|
+
require 'thread'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# FIXME: credits @ daemonize lib
|
|
23
|
+
class Butler
|
|
24
|
+
class Bot < IRC::Client
|
|
25
|
+
include Log::Comfort
|
|
26
|
+
|
|
27
|
+
attr_reader :access
|
|
28
|
+
attr_reader :base
|
|
29
|
+
attr_reader :config
|
|
30
|
+
attr_reader :log_device
|
|
31
|
+
attr_reader :path
|
|
32
|
+
attr_reader :plugins
|
|
33
|
+
attr_reader :scheduler
|
|
34
|
+
|
|
35
|
+
# FIXME, raise if selftest fails
|
|
36
|
+
def initialize(path, name, opts={})
|
|
37
|
+
path ||= Butler.path
|
|
38
|
+
@irc = nil # early inspects
|
|
39
|
+
@name = name
|
|
40
|
+
@base = "#{path.bots}/#{name}"
|
|
41
|
+
@path = OpenStruct.new(
|
|
42
|
+
:access => @base+'/access',
|
|
43
|
+
:base => @base,
|
|
44
|
+
:config => @base+'/config',
|
|
45
|
+
:log => @base+'/log',
|
|
46
|
+
:plugins => @base+'/plugins',
|
|
47
|
+
:strings => @base+'/strings'
|
|
48
|
+
)
|
|
49
|
+
@log_device = $stderr
|
|
50
|
+
@config = Configuration.new(@base+'/config')
|
|
51
|
+
@scheduler = Scheduler.new
|
|
52
|
+
@access = Access.new(
|
|
53
|
+
Access::YAMLBase.new(Access::User::Base, @base+'/access/user'),
|
|
54
|
+
Access::YAMLBase.new(Access::Role::Base, @base+'/access/role'),
|
|
55
|
+
Access::YAMLBase.new(Access::Privilege::Base, @base+'/access/privilege')
|
|
56
|
+
#:channel => Access::YAMLBase.new("#{TestDir}/channel", Access::Location)
|
|
57
|
+
)
|
|
58
|
+
@on_disconnect = nil
|
|
59
|
+
@on_reconnect = nil
|
|
60
|
+
@reconnect = [
|
|
61
|
+
opts.delete(:reconnect_delay) || 60,
|
|
62
|
+
opts.delete(:reconnect_tries) || -1
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
super(@config["connections.main.server"], {
|
|
66
|
+
:port => @config["connections.main.port"],
|
|
67
|
+
:host => @config["connections.main.host"]
|
|
68
|
+
}.merge(opts))
|
|
69
|
+
|
|
70
|
+
# { lang => { trigger => SortedSet[ *commands ] } }
|
|
71
|
+
@commands = {}
|
|
72
|
+
@plugins = Plugins.new(self, @base+'/plugins')
|
|
73
|
+
|
|
74
|
+
subscribe(:PRIVMSG, -10, &method(:invoke_commands))
|
|
75
|
+
subscribe(:NOTICE, -10, &method(:invoke_commands))
|
|
76
|
+
|
|
77
|
+
selftest
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def invoke_commands(listener, message)
|
|
81
|
+
return unless ((sequence = invocation(message.text)) || message.realm == :private)
|
|
82
|
+
message.invocation = sequence || ""
|
|
83
|
+
message.language = "en"
|
|
84
|
+
trigger = message.arguments.first
|
|
85
|
+
access = message.from && message.from.access.method(:authorized?)
|
|
86
|
+
return unless access and trigger
|
|
87
|
+
|
|
88
|
+
trigger = trigger.downcase
|
|
89
|
+
commands = []
|
|
90
|
+
if @commands[message.language] && @commands[message.language][trigger] then
|
|
91
|
+
commands.concat(@commands[message.language][trigger].to_a)
|
|
92
|
+
end
|
|
93
|
+
if message.language != "en" && @commands["en"] && @commands["en"][trigger] then
|
|
94
|
+
commands.concat(@commands["en"][trigger].to_a)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
commands.each { |command|
|
|
98
|
+
if args = (command.invoked_by?(message)) then
|
|
99
|
+
if !command.authorization || access.call(command.authorization) then
|
|
100
|
+
Thread.new {
|
|
101
|
+
begin
|
|
102
|
+
command.call(message, *args)
|
|
103
|
+
rescue Exception => e
|
|
104
|
+
exception(e)
|
|
105
|
+
end
|
|
106
|
+
}
|
|
107
|
+
break if command.abort_invocations?
|
|
108
|
+
else
|
|
109
|
+
info("#{message.from} (#{message.from.access.id}) had no authorization for 'plugin/#{command.plugin.base}'")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def add_command(command)
|
|
116
|
+
@commands[command.language] ||= {}
|
|
117
|
+
@commands[command.language][command.trigger] ||= SortedSet.new
|
|
118
|
+
@commands[command.language][command.trigger].add(command)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def delete_command(command)
|
|
122
|
+
@commands[command.language][command.trigger].delete(command)
|
|
123
|
+
if @commands[command.language][command.trigger].empty? then
|
|
124
|
+
@commands[command.language].delete(command.trigger)
|
|
125
|
+
@commands.delete(command.language) if @commands[command.language].empty?
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# returns the rest of the sequence if it was an invocation, nil else
|
|
130
|
+
def invocation(sequence)
|
|
131
|
+
@myself && sequence[/^(?:!?#{@myself.nick}[:;,])\s+/i]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# runs a diagnose, will collect all wrongs and raise if it finds any
|
|
135
|
+
def selftest
|
|
136
|
+
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def login
|
|
140
|
+
@access.default_user = @access["default_user"]
|
|
141
|
+
@access.default_user.login
|
|
142
|
+
|
|
143
|
+
nick = @config["connections.main.nick"]
|
|
144
|
+
pass = @config["connections.main.password"]
|
|
145
|
+
super(
|
|
146
|
+
nick,
|
|
147
|
+
@config["connections.main.user"],
|
|
148
|
+
@config["connections.main.real"]
|
|
149
|
+
)
|
|
150
|
+
info("Logged in")
|
|
151
|
+
# FIXME, use #same_nick?
|
|
152
|
+
if pass && @myself.nick.downcase != nick.downcase then
|
|
153
|
+
@irc.ghost(nick, pass)
|
|
154
|
+
sleep(1) # FIXME, do a wait_for or similar
|
|
155
|
+
@irc.nick(nick)
|
|
156
|
+
end
|
|
157
|
+
@irc.identify(pass) if pass
|
|
158
|
+
join(*@config["connections.main.channels"])
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def on_disconnect(reason)
|
|
162
|
+
return if reason == :quit
|
|
163
|
+
unless @reconnect[1].zero? then
|
|
164
|
+
@reconnect[1] -= 1
|
|
165
|
+
sleep(@reconnect[0])
|
|
166
|
+
login
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def output_to_logfiles
|
|
172
|
+
# Errors still go to $stderr, $stdin handles puts as "info" level $stderr prints
|
|
173
|
+
@log_device = Log.file(@path.log+'/error.log')
|
|
174
|
+
$stdout = Log.forward(@log_device, :warn)
|
|
175
|
+
$stderr = Log.forward(@log_device, :info)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def inspect # :nodoc:
|
|
179
|
+
"#<%s:0x%08x %s (in %s) irc=%s>" % [
|
|
180
|
+
self.class,
|
|
181
|
+
object_id << 1,
|
|
182
|
+
@name,
|
|
183
|
+
@base,
|
|
184
|
+
@irc.inspect
|
|
185
|
+
]
|
|
186
|
+
end
|
|
187
|
+
end # Bot
|
|
188
|
+
|
|
189
|
+
class IRC::Users
|
|
190
|
+
attr_reader :client
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class IRC::User
|
|
194
|
+
attr_accessor :access
|
|
195
|
+
|
|
196
|
+
def authorized?(*args)
|
|
197
|
+
@access.authorized?(*args)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
alias butler_initialize initialize unless method_defined? :butler_initialize
|
|
201
|
+
def initialize(users, *args, &block)
|
|
202
|
+
butler_initialize(users, *args, &block)
|
|
203
|
+
@access = users.client.access.default_user
|
|
204
|
+
end
|
|
205
|
+
end # IRC::User
|
|
206
|
+
|
|
207
|
+
class IRC::Message
|
|
208
|
+
# the language of this message, as determined by butler
|
|
209
|
+
attr_accessor :language
|
|
210
|
+
|
|
211
|
+
# the invocation-sequence (e.g. "butler, "), nil if none was used (e.g. in
|
|
212
|
+
# private messages)
|
|
213
|
+
attr_accessor :invocation
|
|
214
|
+
|
|
215
|
+
alias butler_initialize initialize unless method_defined? :butler_initialize
|
|
216
|
+
def initialize(*args, &block)
|
|
217
|
+
butler_initialize(*args, &block)
|
|
218
|
+
@language = nil
|
|
219
|
+
@invocation = nil
|
|
220
|
+
@arguments = nil
|
|
221
|
+
@post_arguments = nil
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# parses a given string into argument-tokens. a token is either a word or a quoted string. escapes are respected.
|
|
225
|
+
# e.g. 'Hallo "this is token2" "and this \"token\" is token3"'
|
|
226
|
+
# would be parsed into: ["hallo", "this is token2", "and this \"token\" is token3"]
|
|
227
|
+
def arguments
|
|
228
|
+
return [] unless text # only messages with text can be tokenized to parameters
|
|
229
|
+
@arguments ||= begin # don't do double-work
|
|
230
|
+
text[@invocation.length..-1].arguments
|
|
231
|
+
rescue String::SingleQuoteException => ex
|
|
232
|
+
ex.pre+[ex.post.join(" ")]
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# FIXME due to it's current working, post_arguments will strip formatting
|
|
237
|
+
# Returns the rest of message text after argument
|
|
238
|
+
# == Synopsis
|
|
239
|
+
# "foo bar baz".post_arguments[0] # => "foo bar baz"
|
|
240
|
+
# "foo bar baz".post_arguments[1] # => "bar baz"
|
|
241
|
+
# "foo bar baz".post_arguments[2] # => "baz"
|
|
242
|
+
def post_arguments
|
|
243
|
+
return [] unless text # only messages with text can be tokenized to parameters
|
|
244
|
+
@post_arguments ||= text[@invocation.length..-1].post_arguments # don't do double-work
|
|
245
|
+
end
|
|
246
|
+
end # IRC::Message
|
|
247
|
+
end # Butler
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# See LICENSE.txt for permissions.
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
require 'ruby/file/write'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Butler
|
|
14
|
+
IsGem = true # REPLACE: IsGem = false
|
|
15
|
+
|
|
16
|
+
# this module provides services to botcontrol
|
|
17
|
+
# for gems, this module uses the Gem module to get information on the where-
|
|
18
|
+
# abouts of data. If installed using rake from tarball, it is hard-coded.
|
|
19
|
+
class Control
|
|
20
|
+
attr_reader :dialog
|
|
21
|
+
attr_reader :config
|
|
22
|
+
attr_reader :path
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
@path = OpenStruct.new({
|
|
26
|
+
:template => Gem.datadir("butler")+'/config_template.yaml', # DELETE
|
|
27
|
+
:config => Gem.datadir("butler")+'/config.yaml', # REPLACE: :config => %CONFIG_DIR%+'/config.yaml',
|
|
28
|
+
:dialogs => Gem.datadir("butler")+'/dialogs', # REPLACE: :dialogs => %DIALOGS_DIR%,
|
|
29
|
+
:plugins => Gem.datadir("butler")+'/plugins', # REPLACE: :plugins => %PLUGINS_DIR%,
|
|
30
|
+
:strings => Gem.datadir("butler")+'/strings', # REPLACE: :strings => %STRINGS_DIR%,
|
|
31
|
+
:man => Gem.datadir("butler")+'/man1', # DELETE
|
|
32
|
+
})
|
|
33
|
+
File.write(@path.config, File.read(@path.template)) unless File.exist?(@path.config)
|
|
34
|
+
@config = OpenStruct.new(YAML.load_file(@path.config))
|
|
35
|
+
@dialog = DialogLine.new(@path.dialogs, @config.language)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def user(name=nil)
|
|
39
|
+
# use Etc.getlogin?
|
|
40
|
+
name || ENV['SUDO_USER'] || ENV['USER'] || @dialog.discuss("username", false)[:name]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def configured?(user=nil)
|
|
44
|
+
path = @config.users[user(user)]
|
|
45
|
+
path && File.exist?(path)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def configure_user(user=nil)
|
|
49
|
+
raise Errno::EACCES unless File.writable?(@path.config)
|
|
50
|
+
user ||= user()
|
|
51
|
+
installer = Installer.new("butler")
|
|
52
|
+
user_config = @dialog.discuss(:botcontrol, false,
|
|
53
|
+
:installer => installer,
|
|
54
|
+
:user => user,
|
|
55
|
+
:path => path
|
|
56
|
+
)
|
|
57
|
+
path = @config.users[user] = user_config.delete(:config)+"/config.yaml"
|
|
58
|
+
user_config[:language] = "en" # FIXME multilingualize
|
|
59
|
+
update_config
|
|
60
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
61
|
+
FileUtils.chown(user, nil, File.dirname(path))
|
|
62
|
+
File.write(path, user_config.to_yaml)
|
|
63
|
+
FileUtils.chown(user, nil, path)
|
|
64
|
+
OpenStruct.new(@config.users[user])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def update_config
|
|
68
|
+
File.write(@path.config, @config.to_hash.to_yaml)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def user_config(user=nil)
|
|
72
|
+
path = @config.users[user(user)]
|
|
73
|
+
user_config = OpenStruct.new(YAML.load_file(path))
|
|
74
|
+
user_config.plugin_repository = @path.plugins
|
|
75
|
+
user_config
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def butler_path(user=nil)
|
|
79
|
+
path = @config.users[user(user)]
|
|
80
|
+
user_config = OpenStruct.new(YAML.load_file(path))
|
|
81
|
+
user_config.plugin_repository = @path.plugins
|
|
82
|
+
user_config
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def discuss(dir, lang=nil, variables={}, &block)
|
|
86
|
+
@dialog.discuss(dir, lang, variables.merge(:botcontrol => self), &block)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def language
|
|
90
|
+
"en"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# See LICENSE.txt for permissions.
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
require 'durations'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Butler
|
|
14
|
+
# A message can be converted to a dialog, this will open a query to the
|
|
15
|
+
# originator of the query and intercept every message there
|
|
16
|
+
class Dialog
|
|
17
|
+
@timeout = 5.minutes, :timeout
|
|
18
|
+
@reminder = 3.minutes, :reminder
|
|
19
|
+
|
|
20
|
+
class <<self
|
|
21
|
+
def timeout(after, string)
|
|
22
|
+
@timeout = after, string
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def reminder(after, string)
|
|
26
|
+
@reminder = after, string
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(plugin)
|
|
31
|
+
@plugin = plugin
|
|
32
|
+
@plugin.butler.subscribe(:PRIVMSG, 0, true) { |listener, message|
|
|
33
|
+
if message.private? then
|
|
34
|
+
postpone
|
|
35
|
+
dialog(listener, message)
|
|
36
|
+
:block
|
|
37
|
+
else
|
|
38
|
+
:pass
|
|
39
|
+
end
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def postpone
|
|
44
|
+
@reminder.postpone
|
|
45
|
+
@timeout.postpone
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def dialog(listener, message)
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def timeout(timer, string)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def localize(*args)
|
|
56
|
+
@plugin.localize(*args)
|
|
57
|
+
end
|
|
58
|
+
alias _ localize
|
|
59
|
+
|
|
60
|
+
def method_missing(*args)
|
|
61
|
+
@plugin.__send__(*args)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
Structure = %w[
|
|
11
|
+
BOTPATH
|
|
12
|
+
BOTPATH/access
|
|
13
|
+
BOTPATH/access/privilege
|
|
14
|
+
BOTPATH/access/role
|
|
15
|
+
BOTPATH/access/user
|
|
16
|
+
BOTPATH/log
|
|
17
|
+
BOTPATH/plugins
|
|
18
|
+
BOTPATH/strings
|
|
19
|
+
].map { |e| [e, 0755] }
|
|
20
|
+
# first one is the base-directory
|
|
21
|
+
ConfigurationStructure = %w[
|
|
22
|
+
BOTPATH/config
|
|
23
|
+
connections
|
|
24
|
+
channels
|
|
25
|
+
plugin
|
|
26
|
+
]
|
|
27
|
+
EmptyConfig = {
|
|
28
|
+
'remote' => {
|
|
29
|
+
'host' => 'localhost',
|
|
30
|
+
'port' => 7666,
|
|
31
|
+
'active' => false,
|
|
32
|
+
},
|
|
33
|
+
'logging' => {
|
|
34
|
+
},
|
|
35
|
+
'owner' => {
|
|
36
|
+
'username' => nil, # no owner
|
|
37
|
+
'contact' => 'unknown',
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
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/topic'
|
|
10
|
+
require 'thread'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Butler
|
|
15
|
+
module IRC
|
|
16
|
+
class Channel
|
|
17
|
+
include Comparable
|
|
18
|
+
include Enumerable
|
|
19
|
+
|
|
20
|
+
#the name of the channel (immutable)
|
|
21
|
+
attr_reader :name
|
|
22
|
+
#the current topic of the channel
|
|
23
|
+
attr_reader :topic
|
|
24
|
+
|
|
25
|
+
# the value used for comparison/sorting
|
|
26
|
+
attr_reader :compare
|
|
27
|
+
|
|
28
|
+
# :nodoc:
|
|
29
|
+
attr_reader :hash
|
|
30
|
+
|
|
31
|
+
# Create a Butler::IRC::Channel-object.
|
|
32
|
+
# Use Channel.create(<channelname>) if you can.
|
|
33
|
+
def initialize(all_users, name, topic=nil, topic_set_by=nil, topic_set_at=nil)
|
|
34
|
+
name = name.to_str.strip_user_prefixes
|
|
35
|
+
raise "Invalid channelname '#{name}'" unless name.valid_channelname?
|
|
36
|
+
@all_users = all_users
|
|
37
|
+
@name = name.freeze
|
|
38
|
+
@compare = name.downcase.freeze
|
|
39
|
+
@hash = @compare.hash
|
|
40
|
+
@users = {}
|
|
41
|
+
@topic = Topic.new(topic || "", topic_set_by, topic_set_at)
|
|
42
|
+
@lock = Mutex.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Looks up if there are clones in the list of users
|
|
46
|
+
# min defines how many users need to have the same host (+user if strong)
|
|
47
|
+
# to appear in the list.
|
|
48
|
+
def clones(strong=false, min=2)
|
|
49
|
+
sieve = Hash.new { |h,k| h[k] = [] }
|
|
50
|
+
if strong then
|
|
51
|
+
users.each { |user|
|
|
52
|
+
sieve["#{user.user}@#{user.host}"] << user if (user.user && user.host)
|
|
53
|
+
}
|
|
54
|
+
else
|
|
55
|
+
users = users()
|
|
56
|
+
users.each { |user|
|
|
57
|
+
sieve[user.host] << user if user.host
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
sieve.select { |host, users| users.length >= min }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
#List of users (Butler::IRC::User objects) in this channel
|
|
64
|
+
def users
|
|
65
|
+
@all_users.map_nicks(*@users.keys)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# List of normalized (lowercased and flag-stripped) nicknames in this channel
|
|
69
|
+
def nicks
|
|
70
|
+
@users.keys
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Iterate over all users in this channel
|
|
74
|
+
def each
|
|
75
|
+
@users.each_value { |user| yield @all_users[user] }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns amount of users
|
|
79
|
+
def length
|
|
80
|
+
@users.length
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Comparison based on lowercase channelname
|
|
84
|
+
def ==(other)
|
|
85
|
+
@compare == other.to_str.downcase
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Comparison based on lowercase channelname, -1, 0 or 1
|
|
89
|
+
def <=>(other)
|
|
90
|
+
@compare <=> other.to_str.downcase
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Add a user to the channel, see Butler::IRC::CHANNEL_SIGNALS for possible
|
|
94
|
+
# reasons (should only be used by IRC::Parser)
|
|
95
|
+
def add_user(user, reason) #:nodoc:
|
|
96
|
+
@users[user.to_str.downcase] ||= 0
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get user by nick if or nil if that user is not in this channel
|
|
100
|
+
def [](nick)
|
|
101
|
+
nick = nick.to_str.downcase
|
|
102
|
+
@users.has_key?(nick) ? @all_users[nick] : nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Test if user with nick is in this channel (also works with IRC::User objects)
|
|
106
|
+
def include?(nick)
|
|
107
|
+
@users.has_key?(nick.to_str.downcase)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Remove a user from the channel, see Butler::IRC::CHANNEL_SIGNALS for
|
|
111
|
+
# possible reasons (should only be used by Butler::IRC::Parser)
|
|
112
|
+
def delete_user(user, reason) #:nodoc:
|
|
113
|
+
@users.delete(user.to_str.downcase)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def inspect # :nodoc:
|
|
117
|
+
"#<%s:0x%x %s (%d users)" % [self.class, object_id, @name, length]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Returns the (frozen!) name of the channel
|
|
121
|
+
def to_s
|
|
122
|
+
@name
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns the normalized name of the channel
|
|
126
|
+
def to_str #:nodoc:
|
|
127
|
+
@compare
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def eql?(other) #:nodoc:
|
|
131
|
+
self.class == other.class && @compare == other.compare
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|