butler 1.8.2 → 1.8.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/bin/botcontrol +1 -1
- data/data/butler/dialogs/create_config.rb +2 -2
- data/data/butler/dialogs/quickcreate.rb +6 -4
- data/data/butler/dialogs/uninstall.rb +4 -3
- data/data/butler/plugins/core/access.rb +10 -10
- data/data/butler/plugins/dev/bleakhouse.rb +19 -8
- data/data/butler/plugins/operator/deop.rb +10 -1
- data/data/butler/plugins/operator/devoice.rb +9 -0
- data/data/butler/plugins/operator/limit.rb +12 -0
- data/data/butler/plugins/operator/op.rb +9 -0
- data/data/butler/plugins/operator/voice.rb +10 -0
- data/data/butler/plugins/util/calculator.rb +11 -0
- data/data/butler/services/org.rubyforge.butler/calculator/1/calculator.rb +68 -0
- data/data/butler/services/org.rubyforge.butler/log/1/service.rb +198 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/acknowledge.yaml +8 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/gratitude.yaml +3 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/hello.yaml +6 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance.yaml +7 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance_about.yaml +3 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/insult.yaml +3 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/rejection.yaml +12 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/service.rb +50 -0
- data/lib/access.rb +6 -3
- data/lib/access/privilege.rb +9 -78
- data/lib/access/privilegelist.rb +75 -0
- data/lib/access/role.rb +14 -94
- data/lib/access/role/base.rb +40 -0
- data/lib/access/rolelist.rb +99 -0
- data/lib/access/savable.rb +6 -3
- data/lib/access/user.rb +21 -19
- data/lib/access/version.rb +17 -0
- data/lib/access/yamlbase.rb +64 -48
- data/lib/butler.rb +1 -0
- data/lib/butler/bot.rb +8 -2
- data/lib/butler/control.rb +3 -1
- data/lib/butler/initialvalues.rb +1 -1
- data/lib/butler/irc/client.rb +6 -0
- data/lib/butler/irc/message.rb +14 -9
- data/lib/butler/irc/parser.rb +8 -5
- data/lib/butler/irc/parser/generic.rb +33 -1
- data/lib/butler/irc/parser/rfc2812.rb +5 -2
- data/lib/butler/plugin.rb +22 -2
- data/lib/butler/plugins.rb +2 -7
- data/lib/butler/service.rb +73 -0
- data/lib/butler/services.rb +65 -0
- data/lib/butler/version.rb +1 -1
- data/lib/ruby/array/random.rb +17 -0
- data/lib/ruby/string/camelcase.rb +14 -0
- data/test/test_access.rb +164 -59
- data/test/test_access/privilege/banners.statistics.yaml +3 -0
- data/test/test_access/privilege/banners.yaml +3 -0
- data/test/test_access/privilege/news.create.yaml +3 -0
- data/test/test_access/privilege/news.delete.yaml +3 -0
- data/test/test_access/privilege/news.edit.yaml +3 -0
- data/test/test_access/privilege/news.read.yaml +3 -0
- data/test/test_access/privilege/news.yaml +3 -0
- data/test/test_access/privilege/paid_content.yaml +3 -0
- data/test/test_access/privilege/statistics.ftp.yaml +3 -0
- data/test/test_access/privilege/statistics.web.yaml +3 -0
- data/test/test_access/privilege/statistics.yaml +3 -0
- data/test/test_access/role/chiefeditor.yaml +7 -0
- data/test/test_access/role/editor.yaml +9 -0
- data/test/test_access/user/test.yaml +12 -0
- metadata +51 -5
- data/data/butler/plugins/core/user.rb +0 -166
- data/data/butler/plugins/dev/onhandlers.rb +0 -93
- data/data/butler/plugins/service/log.rb +0 -183
data/lib/butler.rb
CHANGED
data/lib/butler/bot.rb
CHANGED
@@ -12,6 +12,7 @@ require 'butler/debuglog'
|
|
12
12
|
require 'configuration'
|
13
13
|
require 'log'
|
14
14
|
require 'butler/plugins'
|
15
|
+
require 'butler/services'
|
15
16
|
require 'butler/session'
|
16
17
|
require 'ruby/string/arguments'
|
17
18
|
require 'ruby/string/post_arguments'
|
@@ -33,6 +34,7 @@ class Butler
|
|
33
34
|
attr_reader :path
|
34
35
|
attr_reader :plugins
|
35
36
|
attr_reader :scheduler
|
37
|
+
attr_reader :services
|
36
38
|
|
37
39
|
# FIXME, raise if selftest fails
|
38
40
|
def initialize(path, name, opts={})
|
@@ -47,6 +49,7 @@ class Butler
|
|
47
49
|
:lib => @base+'/lib',
|
48
50
|
:log => @base+'/log',
|
49
51
|
:plugins => @base+'/plugins',
|
52
|
+
:services => @base+'/services',
|
50
53
|
:strings => @base+'/strings'
|
51
54
|
)
|
52
55
|
$LOAD_PATH.unshift(@path.lib)
|
@@ -74,7 +77,9 @@ class Butler
|
|
74
77
|
|
75
78
|
# { lang => { trigger => SortedSet[ *commands ] } }
|
76
79
|
@commands = {}
|
77
|
-
@
|
80
|
+
@services = Services.new(self, @path.services)
|
81
|
+
@services.load_all
|
82
|
+
@plugins = Plugins.new(self, @path.plugins)
|
78
83
|
|
79
84
|
if $DEBUG then
|
80
85
|
@irc.extend DebugLog
|
@@ -117,7 +122,7 @@ class Butler
|
|
117
122
|
}
|
118
123
|
break if command.abort_invocations?
|
119
124
|
else
|
120
|
-
info("#{message.from} (#{message.from.access.
|
125
|
+
info("#{message.from} (#{message.from.access.oid}) had no authorization for 'plugin/#{command.plugin.base}'")
|
121
126
|
end
|
122
127
|
end
|
123
128
|
}
|
@@ -150,6 +155,7 @@ class Butler
|
|
150
155
|
def login
|
151
156
|
@access.default_user = @access["default_user"]
|
152
157
|
@access.default_user.login
|
158
|
+
p @access.default_user
|
153
159
|
|
154
160
|
nick = @config["connections/main/nick"]
|
155
161
|
pass = @config["connections/main/password"]
|
data/lib/butler/control.rb
CHANGED
@@ -27,6 +27,7 @@ class Butler
|
|
27
27
|
:config => Gem.datadir("butler")+'/config.yaml', # REPLACE: :config => %CONFIG_DIR%+'/config.yaml',
|
28
28
|
:dialogs => Gem.datadir("butler")+'/dialogs', # REPLACE: :dialogs => %DIALOGS_DIR%,
|
29
29
|
:plugins => Gem.datadir("butler")+'/plugins', # REPLACE: :plugins => %PLUGINS_DIR%,
|
30
|
+
:services => Gem.datadir("butler")+'/services', # REPLACE: :services => %SERVICES_DIR%,
|
30
31
|
:strings => Gem.datadir("butler")+'/strings', # REPLACE: :strings => %STRINGS_DIR%,
|
31
32
|
:man => Gem.datadir("butler")+'/man1', # DELETE
|
32
33
|
})
|
@@ -78,7 +79,8 @@ class Butler
|
|
78
79
|
def butler_path(user=nil)
|
79
80
|
path = @config.users[user(user)]
|
80
81
|
user_config = OpenStruct.new(YAML.load_file(path))
|
81
|
-
user_config.plugin_repository
|
82
|
+
user_config.plugin_repository = @path.plugins
|
83
|
+
user_config.service_repository = @path.services
|
82
84
|
user_config
|
83
85
|
end
|
84
86
|
|
data/lib/butler/initialvalues.rb
CHANGED
data/lib/butler/irc/client.rb
CHANGED
@@ -120,6 +120,12 @@ class Butler
|
|
120
120
|
@myself = @users.myself
|
121
121
|
|
122
122
|
subscribe(:PING, 100) { |listener, message| @irc.pong(message.pong) }
|
123
|
+
subscribe(:ISUPPORT) { |listener, message|
|
124
|
+
# @parser. ...
|
125
|
+
}
|
126
|
+
subscribe(:RPL_IDENTIFY_MSG) { |listener, message|
|
127
|
+
@parser.msg_identify = true
|
128
|
+
}
|
123
129
|
end
|
124
130
|
|
125
131
|
# Load an additional command-set for @parser
|
data/lib/butler/irc/message.rb
CHANGED
@@ -46,15 +46,16 @@ class Butler
|
|
46
46
|
|
47
47
|
#specific data
|
48
48
|
@fields = Hash.new { |h,k| raise IndexError, "No member '#{k}' available." }.merge({
|
49
|
-
:raw
|
50
|
-
:prefix
|
51
|
-
:command
|
52
|
-
:params
|
53
|
-
:symbol
|
54
|
-
:from
|
55
|
-
:for
|
56
|
-
:channel
|
57
|
-
:text
|
49
|
+
:raw => @raw,
|
50
|
+
:prefix => @prefix,
|
51
|
+
:command => @command,
|
52
|
+
:params => @params,
|
53
|
+
:symbol => @symbol,
|
54
|
+
:from => nil,
|
55
|
+
:for => nil,
|
56
|
+
:channel => nil,
|
57
|
+
:text => nil,
|
58
|
+
:identified => nil,
|
58
59
|
})
|
59
60
|
end
|
60
61
|
|
@@ -77,6 +78,10 @@ class Butler
|
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
81
|
+
def identified?
|
82
|
+
@fields[:identified]
|
83
|
+
end
|
84
|
+
|
80
85
|
def from
|
81
86
|
@fields[:from]
|
82
87
|
end
|
data/lib/butler/irc/parser.rb
CHANGED
@@ -63,14 +63,17 @@ class Butler
|
|
63
63
|
attr_reader :users
|
64
64
|
attr_reader :channels
|
65
65
|
attr_reader :commands
|
66
|
+
|
67
|
+
attr_accessor :msg_identify
|
66
68
|
|
67
69
|
def initialize(client, users, channels, *command_sets)
|
68
|
-
@client
|
69
|
-
@users
|
70
|
-
@channels
|
71
|
-
@commands
|
70
|
+
@client = client
|
71
|
+
@users = users
|
72
|
+
@channels = channels
|
73
|
+
@commands = Commands.new(*command_sets)
|
74
|
+
@msg_identify = false
|
72
75
|
end
|
73
|
-
|
76
|
+
|
74
77
|
# parses an incomming message and returns a Message object from which you
|
75
78
|
# can easily access parsed data.
|
76
79
|
# Expects the newlines to be already chomped off.
|
@@ -9,6 +9,33 @@
|
|
9
9
|
# A list with all common, but non-rfc2812 IRC-Commands and their parsing
|
10
10
|
# instructions.
|
11
11
|
|
12
|
+
#
|
13
|
+
alter("005", :ISUPPORT) { |message, parser|
|
14
|
+
hash = {
|
15
|
+
"CHANNELLEN" => 50,
|
16
|
+
"NICKLEN" => 8,
|
17
|
+
}
|
18
|
+
message.params.sub(/\s+:.*?$/, '').split(/ /).each { |support|
|
19
|
+
name, value = support.split(/=/,2)
|
20
|
+
hash[name] = case value
|
21
|
+
when nil
|
22
|
+
true
|
23
|
+
when "NICKLEN"
|
24
|
+
value.to_i
|
25
|
+
when "PREFIX"
|
26
|
+
modes, prefixes = value[1..-1].split(/\)/, 2)
|
27
|
+
value = {}
|
28
|
+
modes.split(//).zip(prefixes.split(//)) { |k,v| value[k] = v }
|
29
|
+
value
|
30
|
+
when "MAXCHANNELS"
|
31
|
+
value.to_i
|
32
|
+
else value
|
33
|
+
end
|
34
|
+
}
|
35
|
+
message.create_member(:support, hash)
|
36
|
+
p hash
|
37
|
+
}
|
38
|
+
|
12
39
|
# Seen:
|
13
40
|
# - ConferenceRoom (irc.bluewin.ch)
|
14
41
|
add("007", :UNK_007)
|
@@ -26,7 +53,12 @@ add("266", :RPL_GLOBALUSERS)
|
|
26
53
|
|
27
54
|
# Seen:
|
28
55
|
# - hyperion-1.0.2b (irc.freenode.net)
|
29
|
-
|
56
|
+
# States what kind of messages will be identified
|
57
|
+
add("290", :RPL_IDENTIFY_MSG) { |message, parser|
|
58
|
+
types = {}
|
59
|
+
message.params.scan(/\S+/).each { |k| types[k] = true }
|
60
|
+
message.create_member(:types, types)
|
61
|
+
}
|
30
62
|
|
31
63
|
# Seen:
|
32
64
|
# - ConferenceRoom (irc.bluewin.ch), sent if a nick is registered
|
@@ -79,6 +79,9 @@ add("part", :PART, /^([^\x00\x07\x10\x0D\x20,:]+)(?: :(.*))?/, [:channel,
|
|
79
79
|
add("ping", :PING, /:(.*)/, [:pong])
|
80
80
|
add("pong", :PONG)
|
81
81
|
add("privmsg", :PRIVMSG, /(\S+) :(.*)/, [:for, :text]) { |message, parser|
|
82
|
+
if parser.msg_identify && message.text.slice!(/^[+-]/) == '+' then
|
83
|
+
message.alter_member(:identified, true)
|
84
|
+
end
|
82
85
|
if message.channel then
|
83
86
|
message.create_member(:realm, :public)
|
84
87
|
message.create_method(:private?) { false }
|
@@ -104,8 +107,8 @@ add("topic", :TOPIC, /(\S+) :(.*)/, [:channel, :text])
|
|
104
107
|
add("001", :RPL_WELCOME)
|
105
108
|
add("002", :RPL_YOURHOST)
|
106
109
|
add("003", :RPL_CREATED)
|
107
|
-
add("004", :RPL_MYINFO)
|
108
|
-
add("005", :
|
110
|
+
add("004", :RPL_MYINFO, /(\S+) (\S+) (\S+) (\S+)/, [:servername, :version, :user_modes, :channel_modes])
|
111
|
+
add("005", :RPL_BOUNCE)
|
109
112
|
|
110
113
|
|
111
114
|
|
data/lib/butler/plugin.rb
CHANGED
@@ -82,12 +82,28 @@ class Butler
|
|
82
82
|
info("Loaded plugin '#{@base}' (#{self})")
|
83
83
|
end
|
84
84
|
|
85
|
-
|
85
|
+
# a class instance variable for the plugin
|
86
|
+
# defines attr_readers for it
|
87
|
+
def plugin_attribute(*names)
|
88
|
+
(class <<self; self; end).instance_eval {
|
89
|
+
attr_reader(*names)
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
# a class instance variable for the plugin,
|
94
|
+
# defines attr_accessors for it
|
95
|
+
def plugin_accessor(*names)
|
96
|
+
(class <<self; self; end).instance_eval {
|
97
|
+
attr_accessor(*names)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def create_templates(tmpl, name) # :nodoc:
|
86
102
|
tmpl.each { |key,value|
|
87
103
|
begin
|
88
104
|
tmpl[key] = Templater.new(value)
|
89
105
|
rescue Exception => e
|
90
|
-
e.extend Exception::
|
106
|
+
e.extend Exception::Detailed
|
91
107
|
e.prepend "Could not map #{key} in #{@base} (#{name})."
|
92
108
|
exception(e)
|
93
109
|
end
|
@@ -209,6 +225,10 @@ class Butler
|
|
209
225
|
end
|
210
226
|
end
|
211
227
|
|
228
|
+
def service(name)
|
229
|
+
@butler.services[name]
|
230
|
+
end
|
231
|
+
|
212
232
|
def unknown(n=-1)
|
213
233
|
"Unknown argument #{@message.arguments[n]} for #{@message.arguments[0...n].join(' ')}"
|
214
234
|
end
|
data/lib/butler/plugins.rb
CHANGED
@@ -12,16 +12,10 @@ require 'butler/dialog'
|
|
12
12
|
require 'iterator'
|
13
13
|
require 'log/comfort'
|
14
14
|
require 'ruby/exception/detailed'
|
15
|
+
require 'ruby/string/camelcase'
|
15
16
|
|
16
17
|
|
17
18
|
|
18
|
-
class String
|
19
|
-
# CamelCase a string, e.g. "foo_bar" becomes "FooBar"
|
20
|
-
def camelcase
|
21
|
-
scan(/[^_]+/).map { |s| s.capitalize }.join("")
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
19
|
class Butler
|
26
20
|
# Plugins manages the plugins in Butler
|
27
21
|
# It uses
|
@@ -38,6 +32,7 @@ class Butler
|
|
38
32
|
@plugins = {}
|
39
33
|
@constants = {}
|
40
34
|
@butler = butler
|
35
|
+
@logger = butler.logger
|
41
36
|
end
|
42
37
|
|
43
38
|
# returns a list with first-level groups
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'log/comfort'
|
10
|
+
require 'ostructfixed'
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
class Butler
|
15
|
+
module Service
|
16
|
+
include Log::Comfort
|
17
|
+
|
18
|
+
attr_reader :butler
|
19
|
+
attr_reader :message
|
20
|
+
attr_reader :name
|
21
|
+
attr_reader :rdn
|
22
|
+
attr_reader :version
|
23
|
+
|
24
|
+
# this method is called to initialize the plugin-class,
|
25
|
+
# do not override
|
26
|
+
def load_service(butler, path, rdn, name, version) # :nodoc:
|
27
|
+
@butler = butler
|
28
|
+
@path = OpenStruct.new(
|
29
|
+
:base => path,
|
30
|
+
:fixtures => path+'/fixtures.rb'
|
31
|
+
)
|
32
|
+
@name = name
|
33
|
+
@rdn = rdn
|
34
|
+
@version = version
|
35
|
+
@listener = []
|
36
|
+
load(@path.fixtures) if File.exist?(@path.fixtures)
|
37
|
+
end
|
38
|
+
|
39
|
+
def register(object)
|
40
|
+
@butler.services.register(self, object)
|
41
|
+
end
|
42
|
+
|
43
|
+
def every(*args, &block)
|
44
|
+
@butler.scheduler.every(*args, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def at(*args, &block)
|
48
|
+
@butler.scheduler.at(*args, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def timed(*args, &block)
|
52
|
+
@butler.scheduler.timed(*args, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def subscribe(*args, &block)
|
56
|
+
listener = @butler.subscribe(*args, &block)
|
57
|
+
@listener << listener
|
58
|
+
listener
|
59
|
+
end
|
60
|
+
|
61
|
+
def unload_plugin
|
62
|
+
info("Unloading plugin '#{@base}' (#{self})")
|
63
|
+
@commands.each { |command| @butler.delete_command(command) }
|
64
|
+
@listener.each { |listener| listener.unsubscribe }
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_load(*args); end
|
68
|
+
def on_login(*args); end
|
69
|
+
def on_disconnect(*args); end
|
70
|
+
def on_quit(*args); end
|
71
|
+
def on_unload(*args); end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'butler/service'
|
10
|
+
require 'log/comfort'
|
11
|
+
require 'ruby/exception/detailed'
|
12
|
+
require 'ruby/string/camelcase'
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
class Butler
|
17
|
+
# Plugins manages the plugins in Butler
|
18
|
+
# It uses
|
19
|
+
class Services
|
20
|
+
include Log::Comfort
|
21
|
+
|
22
|
+
def initialize(butler, services_dir)
|
23
|
+
@butler = butler
|
24
|
+
@logger = butler.logger
|
25
|
+
@dir = File.expand_path(services_dir).freeze
|
26
|
+
raise ArgumentError, "#{@dir} is not a directory" unless File.directory?(@dir)
|
27
|
+
@services = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_all
|
31
|
+
Dir.glob("#{@dir}/*/*/*/service.rb") { |file|
|
32
|
+
load(file)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def load(file)
|
37
|
+
rdn, name, version = file.match(%r{([^/]+)/([^/]+)/([^/]+)/service\.rb$}).captures
|
38
|
+
path = File.dirname(file)
|
39
|
+
|
40
|
+
begin
|
41
|
+
constant = "%s_%08X" % [name.camelcase, rand(0xffffffff)]
|
42
|
+
end while Butler::Plugins.const_defined?(constant)
|
43
|
+
service = Butler::Services.const_set(constant, Module.new)
|
44
|
+
service.extend(Butler::Service)
|
45
|
+
service.logger = @butler.logger
|
46
|
+
begin
|
47
|
+
service.load_service(@butler, path, rdn, name, version)
|
48
|
+
service.class_eval(File.read(file), file)
|
49
|
+
service.on_load
|
50
|
+
rescue Exception => e
|
51
|
+
e.extend Exception::Detailed
|
52
|
+
e.prepend "Loading service #{rdn}.#{name} #{version} failed."
|
53
|
+
exception(e)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def register(service, obj)
|
58
|
+
@services[service.name] = obj
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](name)
|
62
|
+
@services[name]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|