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.
- data/CHANGELOG +40 -0
- data/README +9 -9
- data/Rakefile +15 -71
- data/bin/botcontrol +151 -146
- data/data/butler/dialogs/botcontrol.rb +8 -3
- data/data/butler/dialogs/create.rb +18 -18
- data/data/butler/dialogs/create_config.rb +8 -0
- data/data/butler/dialogs/en/create_config.yaml +2 -0
- data/data/butler/dialogs/en/list.yaml +2 -1
- data/data/butler/dialogs/info.rb +2 -2
- data/data/butler/dialogs/list.rb +13 -8
- data/data/butler/plugins/games/countdown.rb +41 -0
- data/data/butler/plugins/games/roll.rb +59 -0
- data/lib/access.rb +6 -107
- data/lib/access/admin.rb +3 -0
- data/lib/access/role.rb +37 -2
- data/lib/access/savable.rb +5 -0
- data/lib/access/user.rb +21 -2
- data/lib/access/yamlbase.rb +4 -0
- data/lib/butler.rb +4 -4
- data/lib/butler/bot.rb +13 -13
- data/lib/butler/irc/client.rb +10 -2
- data/lib/butler/irc/parser.rb +7 -2
- data/lib/butler/irc/parser/commands.rb +24 -7
- data/lib/butler/irc/parser/generic.rb +27 -315
- data/lib/butler/irc/parser/rfc2812.rb +328 -0
- data/lib/butler/irc/socket.rb +1 -1
- data/lib/butler/irc/user.rb +13 -0
- data/lib/butler/plugin.rb +1 -1
- data/lib/butler/plugin/configproxy.rb +4 -4
- data/lib/butler/plugins.rb +1 -1
- data/lib/butler/version.rb +1 -1
- data/lib/configuration.rb +22 -71
- data/lib/dialogline.rb +12 -0
- data/lib/event.rb +5 -2
- data/lib/installer.rb +52 -24
- data/lib/iterator.rb +17 -7
- data/lib/log.rb +32 -5
- data/lib/log/comfort.rb +33 -16
- data/lib/log/entry.rb +25 -5
- data/lib/log/fakeio.rb +1 -0
- data/lib/log/splitter.rb +6 -2
- data/lib/ostructfixed.rb +5 -0
- data/lib/ruby/exception/detailed.rb +3 -3
- data/lib/scheduler.rb +19 -4
- data/lib/scriptfile.rb +9 -2
- data/lib/string.rb +176 -0
- data/lib/string/ascii.rb +31 -0
- data/lib/string/mbencoded.rb +79 -0
- data/lib/string/sbencoded.rb +77 -0
- data/lib/string/utf8.rb +157 -0
- data/lib/templater.rb +68 -10
- data/lib/w3validator.rb +86 -0
- data/test/irc/serverlistings/test_rpl_hiddenhost.txt +60 -0
- data/test/test_access.rb +101 -0
- data/test/test_configuration.rb +63 -0
- metadata +19 -2
@@ -1,4 +1,9 @@
|
|
1
1
|
say(:configuring, :name => variables.user)
|
2
|
-
|
3
|
-
response(:
|
4
|
-
|
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
|
15
|
-
bot.config["connections
|
16
|
-
bot.config["connections
|
17
|
-
bot.config["connections
|
18
|
-
bot.config["connections
|
19
|
-
bot.config["connections
|
20
|
-
bot.config["connections
|
21
|
-
bot.config["connections
|
22
|
-
bot.config["connections
|
23
|
-
bot.config["connections
|
24
|
-
bot.config["connections
|
25
|
-
bot.config["connections
|
26
|
-
bot.config["connections
|
27
|
-
bot.config["connections
|
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
|
30
|
-
bot.config["#{prefix}
|
31
|
-
bot.config["#{prefix}
|
32
|
-
bot.config["#{prefix}
|
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
|
data/data/butler/dialogs/info.rb
CHANGED
@@ -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],
|
data/data/butler/dialogs/list.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
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(:
|
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.
|
data/lib/access.rb
CHANGED
@@ -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
|
data/lib/access/admin.rb
CHANGED
data/lib/access/role.rb
CHANGED
@@ -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
|