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