butler 1.8.2 → 1.8.3
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/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
|