butler 1.8.0 → 1.8.1

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 (57) hide show
  1. data/CHANGELOG +40 -0
  2. data/README +9 -9
  3. data/Rakefile +15 -71
  4. data/bin/botcontrol +151 -146
  5. data/data/butler/dialogs/botcontrol.rb +8 -3
  6. data/data/butler/dialogs/create.rb +18 -18
  7. data/data/butler/dialogs/create_config.rb +8 -0
  8. data/data/butler/dialogs/en/create_config.yaml +2 -0
  9. data/data/butler/dialogs/en/list.yaml +2 -1
  10. data/data/butler/dialogs/info.rb +2 -2
  11. data/data/butler/dialogs/list.rb +13 -8
  12. data/data/butler/plugins/games/countdown.rb +41 -0
  13. data/data/butler/plugins/games/roll.rb +59 -0
  14. data/lib/access.rb +6 -107
  15. data/lib/access/admin.rb +3 -0
  16. data/lib/access/role.rb +37 -2
  17. data/lib/access/savable.rb +5 -0
  18. data/lib/access/user.rb +21 -2
  19. data/lib/access/yamlbase.rb +4 -0
  20. data/lib/butler.rb +4 -4
  21. data/lib/butler/bot.rb +13 -13
  22. data/lib/butler/irc/client.rb +10 -2
  23. data/lib/butler/irc/parser.rb +7 -2
  24. data/lib/butler/irc/parser/commands.rb +24 -7
  25. data/lib/butler/irc/parser/generic.rb +27 -315
  26. data/lib/butler/irc/parser/rfc2812.rb +328 -0
  27. data/lib/butler/irc/socket.rb +1 -1
  28. data/lib/butler/irc/user.rb +13 -0
  29. data/lib/butler/plugin.rb +1 -1
  30. data/lib/butler/plugin/configproxy.rb +4 -4
  31. data/lib/butler/plugins.rb +1 -1
  32. data/lib/butler/version.rb +1 -1
  33. data/lib/configuration.rb +22 -71
  34. data/lib/dialogline.rb +12 -0
  35. data/lib/event.rb +5 -2
  36. data/lib/installer.rb +52 -24
  37. data/lib/iterator.rb +17 -7
  38. data/lib/log.rb +32 -5
  39. data/lib/log/comfort.rb +33 -16
  40. data/lib/log/entry.rb +25 -5
  41. data/lib/log/fakeio.rb +1 -0
  42. data/lib/log/splitter.rb +6 -2
  43. data/lib/ostructfixed.rb +5 -0
  44. data/lib/ruby/exception/detailed.rb +3 -3
  45. data/lib/scheduler.rb +19 -4
  46. data/lib/scriptfile.rb +9 -2
  47. data/lib/string.rb +176 -0
  48. data/lib/string/ascii.rb +31 -0
  49. data/lib/string/mbencoded.rb +79 -0
  50. data/lib/string/sbencoded.rb +77 -0
  51. data/lib/string/utf8.rb +157 -0
  52. data/lib/templater.rb +68 -10
  53. data/lib/w3validator.rb +86 -0
  54. data/test/irc/serverlistings/test_rpl_hiddenhost.txt +60 -0
  55. data/test/test_access.rb +101 -0
  56. data/test/test_configuration.rb +63 -0
  57. metadata +19 -2
@@ -1,4 +1,9 @@
1
1
  say(:configuring, :name => variables.user)
2
- response(:option, :config, variables.installer.user_config_suggestions)
3
- response(:option, :bots, variables.installer.user_data_suggestions.map { |sug| sug+"/bots" })
4
- response(:option, :run, variables.installer.user_pid_suggestions)
2
+ suggestions = variables.installer.user_config_suggestions(variables.user)
3
+ response(:suggestion, :config, suggestions, suggestions.first)
4
+
5
+ suggestions = variables.installer.user_data_suggestions(variables.user).map { |sug| sug+"/bots" }
6
+ response(:suggestion, :bots, suggestions, suggestions.first)
7
+
8
+ suggestions = variables.installer.user_pid_suggestions(variables.user)
9
+ response(:suggestion, :run, suggestions, suggestions.first)
@@ -11,25 +11,25 @@ Butler.create(nil, name)
11
11
  bot = Butler.new(nil, name)
12
12
 
13
13
  #if prompt(:quicksetup, true) then
14
- bot.config["connections.main.server"] = server=ask(:server, "irc.freenode.org", String, :min => 3)
15
- bot.config["connections.main.port"] = 6667
16
- bot.config["connections.main.host"] = nil
17
- bot.config["connections.main.reconnect_delay"] = 60
18
- bot.config["connections.main.reconnect_tries"] = -1
19
- bot.config["connections.main.charset"] = 'utf-8'
20
- bot.config["connections.main.language"] = lang=ask(:language, "en", String, :matching => /\A#[A-Za-z0-9_-]{2,40}\z/)
21
- bot.config["connections.main.nick"] = name
22
- bot.config["connections.main.alternative"] = name[0,7]+"_"
23
- bot.config["connections.main.user"] = name
24
- bot.config["connections.main.real"] = name+" (Butler IRC bot)"
25
- bot.config["connections.main.password"] = ask(:nickpass, nil, String)
26
- bot.config["connections.main.identify"] = :auto
27
- bot.config["connections.main.channels"] = channels=ask(:channels, (server == "irc.freenode.org" ? ["#butler"] : nil), Array, String, :matching => /\A#[^\0\s,]+\z/)
14
+ bot.config["connections/main/server"] = server=ask(:server, "irc.freenode.org", String, :min => 3)
15
+ bot.config["connections/main/port"] = 6667
16
+ bot.config["connections/main/host"] = nil
17
+ bot.config["connections/main/reconnect_delay"] = 60
18
+ bot.config["connections/main/reconnect_tries"] = -1
19
+ bot.config["connections/main/charset"] = 'utf-8'
20
+ bot.config["connections/main/language"] = lang=ask(:language, "en", String, :matching => /\A#[A-Za-z0-9_-]{2,40}\z/)
21
+ bot.config["connections/main/nick"] = name
22
+ bot.config["connections/main/alternative"] = name[0,7]+"_"
23
+ bot.config["connections/main/user"] = name
24
+ bot.config["connections/main/real"] = name+" (Butler IRC bot)"
25
+ bot.config["connections/main/password"] = ask(:nickpass, nil, String)
26
+ bot.config["connections/main/identify"] = :auto
27
+ bot.config["connections/main/channels"] = channels=ask(:channels, (server == "irc.freenode.org" ? ["#butler"] : nil), Array, String, :matching => /\A#[^\0\s,]+\z/)
28
28
  channels.each { |channel|
29
- prefix = "channels.#{server.config_key}.#{channel.config_key}"
30
- bot.config["#{prefix}.password"] = nil
31
- bot.config["#{prefix}.language"] = lang
32
- bot.config["#{prefix}.charset"] = 'utf-8'
29
+ prefix = "channels/#{server.config_key}/#{channel.config_key}"
30
+ bot.config["#{prefix}/password"] = nil
31
+ bot.config["#{prefix}/language"] = lang
32
+ bot.config["#{prefix}/charset"] = 'utf-8'
33
33
  }
34
34
  user = ask(:user, nil, String, :min => 3)
35
35
  pass = ask(:pass, nil, String, :min => 3)
@@ -0,0 +1,8 @@
1
+ begin
2
+ user = variables.user || variables.botcontrol.user
3
+ variables.botcontrol.configure_user unless variables.botcontrol.configured?(user)
4
+ Butler.path = variables.botcontrol.butler_path
5
+ rescue Errno::EACCES
6
+ say :cant_configure, :user => user, :app => $0
7
+ exit
8
+ end
@@ -0,0 +1,2 @@
1
+ ---
2
+ :cant_configure: "Can't configure for '<%= user %>', you should run <%= app %> with sudo."
@@ -1,4 +1,5 @@
1
1
  ---
2
- :nobots: "No bots available."
2
+ :nobots: "<%= user %> has no bots."
3
+ :nouser: "<%= user %> is not registered to botcontrol."
3
4
  :bots: "Available bots (<%= bot_count %>):"
4
5
  :abot: " -<%= bot %>"
@@ -1,5 +1,5 @@
1
- if variables.user && botcontrol.configured?(variables.user) then
2
- user_config = botcontrol.user_config(variables.user)
1
+ if variables.user && variables.botcontrol.configured?(variables.user) then
2
+ user_config = variables.botcontrol.user_config(variables.user)
3
3
  say(:user,
4
4
  :name => variables.user,
5
5
  :bots => user_config[:bots],
@@ -1,10 +1,15 @@
1
- user_config = variables.botcontrol.user_config(variables.user)
2
- bot_list = Butler.list(user_config)
3
- if bot_list.empty? then
4
- say(:nobots)
1
+ user = variables.user || variables.botcontrol.user
2
+ if variables.botcontrol.configured?(user) then
3
+ user_config = variables.botcontrol.user_config(user)
4
+ bot_list = Butler.list(user_config)
5
+ if bot_list.empty? then
6
+ say(:nobots, :user => user)
7
+ else
8
+ say(:bots, :bot_count => bot_list.size)
9
+ bot_list.each { |bot|
10
+ say(:abot, :bot => bot)
11
+ }
12
+ end
5
13
  else
6
- say(:bots, :bot_count => bot_list.size)
7
- bot_list.each { |bot|
8
- say(:abot, :bot => bot)
9
- }
14
+ say(:nouser, :user => user)
10
15
  end
@@ -0,0 +1,41 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ def on_countdown
10
+ return answer(usage) if @message.arguments.length < 2
11
+ answer = Answers[rand(Answers.length)]
12
+ @butler.irc.action(_(:shake), message.channel ? message.channel : message.from)
13
+ sleep(1.5)
14
+ answer(answer)
15
+ end
16
+
17
+ __END__
18
+ ---
19
+ :revision:
20
+ :plugin: 1
21
+ :summary:
22
+ en: Start counting down.
23
+ :about:
24
+ :mail: "apeiros@gmx.net"
25
+ :version: "1.0.0"
26
+ :author: "Stefan Rusterholz"
27
+ :strings:
28
+ :xmap:
29
+ :on_countdown
30
+ :en:
31
+ - countdown :from
32
+ - countdown from :from :to
33
+ - countdown from :from to :to
34
+ - countdown from :from
35
+ :usage:
36
+ en: |
37
+ ![b]countdown![o]
38
+ :help:
39
+ en:
40
+ "": |
41
+ Start counting down.
@@ -0,0 +1,59 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ trigger "roll"
10
+
11
+ def on_trigger
12
+ answer(:roll, :result => roll(@message.post_arguments[1]))
13
+ end
14
+
15
+ def dices(dices, sides)
16
+ dices = 1 if dices.zero?
17
+ sides = 6 if sides.zero?
18
+ (0..dices).inject { |sum,i| sum+rand(sides) }
19
+ end
20
+
21
+ def chance(from, to)
22
+ from = 0 if from.zero?
23
+ to = 100 if to.zero?
24
+ rand(to-from)+from
25
+ end
26
+
27
+ def roll(str)
28
+ str.scan(/(\d*)d(\d*)|(%)(?:\[(\d*)(?::(\d+))?\])?/).map { |dices, sides, percent, from, to|
29
+ percent ? chance(from.to_i, to.to_i) : dices(dices.to_i, sides.to_i)
30
+ }
31
+ end
32
+
33
+
34
+ __END__
35
+ ---
36
+ :revision:
37
+ :plugin: 1
38
+ :summary:
39
+ en: Roll dices.
40
+ :about:
41
+ :mail: "apeiros@gmx.net"
42
+ :version: "1.0.0"
43
+ :author: "Stefan Rusterholz"
44
+ :strings:
45
+ :roll:
46
+ en: "You rolled: <%= result.join(', ') %>"
47
+ :usage:
48
+ en: |
49
+ ![b]roll![o] *![c(blue)]dice serie![o]
50
+ :help:
51
+ en:
52
+ "": |
53
+ Ask butler a question and get an answer from the magic eightball.
54
+ "roll": |
55
+ Roll a series of dices and chances. Try 'roll d 4d10 % %[20:80]'
56
+ "dices": |
57
+ 3d10 rolls 3 dices with 10 sides and shows the sum.
58
+ "chances": |
59
+ Chances are esentially 100 sided dices.
@@ -15,6 +15,11 @@ require 'access/privilege'
15
15
 
16
16
 
17
17
  class Access
18
+ attr_reader :user
19
+ attr_reader :role
20
+ attr_reader :privilege
21
+ attr_accessor :default_user
22
+
18
23
  # =Description
19
24
  # Provides methods to create a user or authenticate
20
25
  # an existing.
@@ -47,11 +52,6 @@ class Access
47
52
  # user.authorized?('news/edit') # => true # only users created via Access#login are authorized
48
53
  # user.authorized?('news/create') # => false
49
54
  #
50
- attr_reader :user
51
- attr_reader :role
52
- attr_reader :privilege
53
- attr_accessor :default_user
54
-
55
55
  def initialize(user, role, privilege)
56
56
  @user = user
57
57
  @role = role
@@ -61,6 +61,7 @@ class Access
61
61
  }
62
62
  end
63
63
 
64
+ # Access users by their id.
64
65
  def [](user_id)
65
66
  @user[user_id]
66
67
  end
@@ -83,105 +84,3 @@ class Access
83
84
  Digest::MD5.hexdigest(credentials+user_id.downcase).upcase
84
85
  end
85
86
  end
86
-
87
- if __FILE__ == $0 then
88
- require 'fileutils'
89
- require 'test/unit'
90
- class TestAccess < Test::Unit::TestCase
91
- TestDir = "test_access"
92
- def setup
93
- #raise "#{TestDir} already exists, aborting test" if File.exist?(TestDir)
94
- _teardown if File.exist?(TestDir)
95
- Dir.mkdir(TestDir)
96
- Dir.mkdir("#{TestDir}/user")
97
- Dir.mkdir("#{TestDir}/role")
98
- Dir.mkdir("#{TestDir}/privilege")
99
- end
100
-
101
- def _teardown
102
- FileUtils.rm_r(TestDir)
103
- end
104
-
105
- def test_all
106
- access = Access.new(
107
- Access::YAMLBase.new(Access::User::Base, "#{TestDir}/user"),
108
- Access::YAMLBase.new(Access::Role::Base, "#{TestDir}/role"),
109
- Access::YAMLBase.new(Access::Privilege::Base, "#{TestDir}/privilege")
110
- #:channel => Access::YAMLBase.new("#{TestDir}/channel", Access::Location)
111
- )
112
- assert(!access.user.exists?("test"))
113
- access.user.create("test", "pass")
114
- assert(access.user.exists?("test"))
115
-
116
- %w(news news/read news/create news/edit news/delete banners banners/statistics
117
- statistics statistics/web statistics/ftp paid_content).each { |priv|
118
- assert(!access.privilege.exists?(priv))
119
- access.privilege.create(priv, "test-privilege #{priv}")
120
- assert(access.privilege.exists?(priv))
121
- }
122
-
123
- access.role.create("editor", "testrole description", %w(news/create news/edit statistics))
124
- access.role.create("chiefeditor", "testrole description 2", %w(news), %w(editor))
125
-
126
- user = access.login("test", "pass")
127
- assert(user)
128
- assert(user.logged?)
129
- assert(!user.active?)
130
- assert(user.inactive?)
131
- user.active = true
132
- assert(user.active?)
133
- assert(!user.inactive?)
134
- user = access.user["test", true]
135
- assert(user)
136
- assert(user.active?)
137
- assert(!user.inactive?)
138
- assert(!user.logged?)
139
-
140
- assert(!user.privileged?("foo"))
141
- assert(!user.privileged?("foo/bar"))
142
- assert(!user.privileged?("foo/baz"))
143
- assert(!user.privileged?("bar"))
144
- assert(!user.privileged?("bar/foo"))
145
- assert(!user.privileged?("baz"))
146
-
147
- assert(!user.authorized?("foo"))
148
- assert(!user.authorized?("foo/bar"))
149
- assert(!user.authorized?("foo/baz"))
150
- assert(!user.authorized?("bar"))
151
- assert(!user.authorized?("bar/foo"))
152
- assert(!user.authorized?("baz"))
153
-
154
- user.privileges.add(%w(banners statistics/web paid_content))
155
-
156
- assert(user.privileged?("banners"))
157
- assert(user.privileged?("banners/statistics"))
158
- assert(user.privileged?("statistics/web"))
159
- assert(!user.privileged?("statistics"))
160
- assert(user.privileged?("bar/foo"))
161
- assert(user.privileged?("baz"))
162
-
163
- assert(!user.authorized?("foo"))
164
- assert(!user.authorized?("foo/bar"))
165
- assert(!user.authorized?("foo/baz"))
166
- assert(!user.authorized?("bar"))
167
- assert(!user.authorized?("bar/foo"))
168
- assert(!user.authorized?("baz"))
169
-
170
- user = access.login("test", "pass")
171
-
172
- assert(user.privileged?("foo"))
173
- assert(user.privileged?("foo/bar"))
174
- assert(user.privileged?("foo/baz"))
175
- assert(!user.privileged?("bar"))
176
- assert(user.privileged?("bar/foo"))
177
- assert(user.privileged?("baz"))
178
-
179
- assert(user.authorized?("foo"))
180
- assert(user.authorized?("foo/bar"))
181
- assert(user.authorized?("foo/baz"))
182
- assert(!user.authorized?("bar"))
183
- assert(user.authorized?("bar/foo"))
184
- assert(user.authorized?("baz"))
185
- end
186
- end
187
- end
@@ -7,7 +7,10 @@
7
7
 
8
8
 
9
9
  class Access
10
+ # Extend Admin users with this plugin, speeds up privileged?
11
+ # by statically returning true and not performing any lookup.
10
12
  module Admin
13
+
11
14
  # admins have all privileges
12
15
  def privileged?(*args)
13
16
  true
@@ -7,11 +7,19 @@
7
7
 
8
8
 
9
9
  class Access
10
+
10
11
  # Access::Role's are a set of privileges with (optionally)
11
12
  # an additional restriction (which is applied globally).
13
+ #
12
14
  class Role
15
+
16
+ # The module to extend the Database manager
13
17
  module Base
18
+
14
19
  # Create a new Role
20
+ # role is a role-id, should be \w+
21
+ # description is a piece of text describing the role
22
+ # privileges are the privileges the role provides
15
23
  def create(role, description=nil, privileges=[])
16
24
  raise "Role #{role} already exists" if exists?(role)
17
25
  role = Role.new(role, description)
@@ -35,9 +43,16 @@ class Access
35
43
  end
36
44
  end
37
45
 
46
+ # The record-id
38
47
  attr_reader :id
48
+
49
+ # The description of the role
39
50
  attr_reader :description
40
51
 
52
+ # Create a new Role
53
+ # role is a role-id, should be \w+
54
+ # description is a piece of text describing the role
55
+ # other: a hash that accepts the keys :privileges and :roles
41
56
  def initialize(role, description=nil, other={})
42
57
  @id = role
43
58
  @privileges = Privileges.new(self, other[:privileges])
@@ -45,6 +60,8 @@ class Access
45
60
  @description = description || "No description"
46
61
  end
47
62
 
63
+ # :nodoc:
64
+ # serialize to column => value for storage
48
65
  def storable
49
66
  {
50
67
  :id => @id,
@@ -54,47 +71,65 @@ class Access
54
71
  }
55
72
  end
56
73
 
74
+ # recursively tests the role and its contained roles if any of them
75
+ # allows a given privilege under given conditions (may be nil to indicate
76
+ # no condition)
57
77
  def allows?(privilege, condition=nil)
58
78
  @privileges.allow?(privilege, condition) || @roles.allow?(privilege, condition)
59
79
  end
60
80
 
81
+ # :nodoc:
61
82
  def eql?(other)
62
83
  self.class == other.class && @id.eql?(other.id)
63
84
  end
64
85
 
86
+ # :nodoc:
65
87
  def hash
66
88
  @id.hash
67
89
  end
68
90
  end
69
91
 
92
+ # A list of roles
70
93
  class Roles
94
+ include Enumerable
95
+
96
+ # owner must be capable of #save
97
+ # roles is the list of Role instances (array)
71
98
  def initialize(owner, roles=nil)
72
99
  @owner = owner
73
100
  @roles = roles || []
74
101
  end
75
-
102
+
103
+ # Tests if any of the roles in self allows a privilege under
104
+ # given conditions (may be nil to indicate no condition)
76
105
  def allow?(privilege, condition=nil)
77
106
  @roles.any? { |role| role.allows?(privilege, condition) }
78
107
  end
79
108
 
109
+ # add a role (Role instance)
80
110
  def add(role)
81
111
  @roles << role
82
112
  @owner.save
83
113
  end
84
114
 
115
+ # delete a role (Role instance)
85
116
  def delete(role)
86
117
  @roles.delete(role)
87
118
  @owner.save
88
119
  end
89
120
 
121
+ # all roles
90
122
  def list
91
- @roles
123
+ @roles.dup
92
124
  end
93
125
 
126
+ # Iterate over the roles
94
127
  def each(&block)
95
128
  @roles.each(&block)
96
129
  end
97
130
 
131
+ # :nodoc:
132
+ # prepare for storage
98
133
  def storable
99
134
  @roles.map { |role| role.id }
100
135
  end