butler 1.8.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
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