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
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
|