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.
Files changed (68) hide show
  1. data/Rakefile +1 -1
  2. data/bin/botcontrol +1 -1
  3. data/data/butler/dialogs/create_config.rb +2 -2
  4. data/data/butler/dialogs/quickcreate.rb +6 -4
  5. data/data/butler/dialogs/uninstall.rb +4 -3
  6. data/data/butler/plugins/core/access.rb +10 -10
  7. data/data/butler/plugins/dev/bleakhouse.rb +19 -8
  8. data/data/butler/plugins/operator/deop.rb +10 -1
  9. data/data/butler/plugins/operator/devoice.rb +9 -0
  10. data/data/butler/plugins/operator/limit.rb +12 -0
  11. data/data/butler/plugins/operator/op.rb +9 -0
  12. data/data/butler/plugins/operator/voice.rb +10 -0
  13. data/data/butler/plugins/util/calculator.rb +11 -0
  14. data/data/butler/services/org.rubyforge.butler/calculator/1/calculator.rb +68 -0
  15. data/data/butler/services/org.rubyforge.butler/log/1/service.rb +198 -0
  16. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/acknowledge.yaml +8 -0
  17. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/gratitude.yaml +3 -0
  18. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/hello.yaml +6 -0
  19. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance.yaml +7 -0
  20. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance_about.yaml +3 -0
  21. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/insult.yaml +3 -0
  22. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/rejection.yaml +12 -0
  23. data/data/butler/services/org.rubyforge.butler/strings/1/service.rb +50 -0
  24. data/lib/access.rb +6 -3
  25. data/lib/access/privilege.rb +9 -78
  26. data/lib/access/privilegelist.rb +75 -0
  27. data/lib/access/role.rb +14 -94
  28. data/lib/access/role/base.rb +40 -0
  29. data/lib/access/rolelist.rb +99 -0
  30. data/lib/access/savable.rb +6 -3
  31. data/lib/access/user.rb +21 -19
  32. data/lib/access/version.rb +17 -0
  33. data/lib/access/yamlbase.rb +64 -48
  34. data/lib/butler.rb +1 -0
  35. data/lib/butler/bot.rb +8 -2
  36. data/lib/butler/control.rb +3 -1
  37. data/lib/butler/initialvalues.rb +1 -1
  38. data/lib/butler/irc/client.rb +6 -0
  39. data/lib/butler/irc/message.rb +14 -9
  40. data/lib/butler/irc/parser.rb +8 -5
  41. data/lib/butler/irc/parser/generic.rb +33 -1
  42. data/lib/butler/irc/parser/rfc2812.rb +5 -2
  43. data/lib/butler/plugin.rb +22 -2
  44. data/lib/butler/plugins.rb +2 -7
  45. data/lib/butler/service.rb +73 -0
  46. data/lib/butler/services.rb +65 -0
  47. data/lib/butler/version.rb +1 -1
  48. data/lib/ruby/array/random.rb +17 -0
  49. data/lib/ruby/string/camelcase.rb +14 -0
  50. data/test/test_access.rb +164 -59
  51. data/test/test_access/privilege/banners.statistics.yaml +3 -0
  52. data/test/test_access/privilege/banners.yaml +3 -0
  53. data/test/test_access/privilege/news.create.yaml +3 -0
  54. data/test/test_access/privilege/news.delete.yaml +3 -0
  55. data/test/test_access/privilege/news.edit.yaml +3 -0
  56. data/test/test_access/privilege/news.read.yaml +3 -0
  57. data/test/test_access/privilege/news.yaml +3 -0
  58. data/test/test_access/privilege/paid_content.yaml +3 -0
  59. data/test/test_access/privilege/statistics.ftp.yaml +3 -0
  60. data/test/test_access/privilege/statistics.web.yaml +3 -0
  61. data/test/test_access/privilege/statistics.yaml +3 -0
  62. data/test/test_access/role/chiefeditor.yaml +7 -0
  63. data/test/test_access/role/editor.yaml +9 -0
  64. data/test/test_access/user/test.yaml +12 -0
  65. metadata +51 -5
  66. data/data/butler/plugins/core/user.rb +0 -166
  67. data/data/butler/plugins/dev/onhandlers.rb +0 -93
  68. data/data/butler/plugins/service/log.rb +0 -183
data/lib/butler.rb CHANGED
@@ -164,6 +164,7 @@ class Butler
164
164
  conf[k] = v
165
165
  }
166
166
  FileUtils.cp_r(path.plugin_repository, bot_path)
167
+ FileUtils.cp_r(path.service_repository, bot_path)
167
168
  end
168
169
 
169
170
  # deletes a bot
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
- @plugins = Plugins.new(self, @base+'/plugins')
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.id}) had no authorization for 'plugin/#{command.plugin.base}'")
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"]
@@ -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 = @path.plugins
82
+ user_config.plugin_repository = @path.plugins
83
+ user_config.service_repository = @path.services
82
84
  user_config
83
85
  end
84
86
 
@@ -15,7 +15,7 @@ class Butler
15
15
  BOTPATH/access/user
16
16
  BOTPATH/log
17
17
  BOTPATH/plugins
18
- BOTPATH/strings
18
+ BOTPATH/services
19
19
  ].map { |e| [e, 0755] }
20
20
  # first one is the base-directory
21
21
  ConfigurationStructure = %w[
@@ -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
@@ -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 => @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,
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
@@ -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 = client
69
- @users = users
70
- @channels = channels
71
- @commands = Commands.new(*command_sets)
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
- add("290", :RPL_IDENTIFY_MSG)
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", :RPL_ISUPPORT)
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
- def create_templates(tmpl, name)
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::Detail
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
@@ -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
@@ -10,7 +10,7 @@ class Butler #:nodoc:
10
10
  module VERSION #:nodoc:
11
11
  MAJOR = 1
12
12
  MINOR = 8
13
- TINY = 2
13
+ TINY = 3
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY].join('.')
16
16
  end