butler 1.8.0

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 (164) hide show
  1. data/CHANGELOG +4 -0
  2. data/GPL.txt +340 -0
  3. data/LICENSE.txt +52 -0
  4. data/README +37 -0
  5. data/Rakefile +334 -0
  6. data/bin/botcontrol +230 -0
  7. data/data/butler/config_template.yaml +4 -0
  8. data/data/butler/dialogs/backup.rb +19 -0
  9. data/data/butler/dialogs/botcontrol.rb +4 -0
  10. data/data/butler/dialogs/config.rb +1 -0
  11. data/data/butler/dialogs/create.rb +53 -0
  12. data/data/butler/dialogs/delete.rb +3 -0
  13. data/data/butler/dialogs/en/backup.yaml +6 -0
  14. data/data/butler/dialogs/en/botcontrol.yaml +5 -0
  15. data/data/butler/dialogs/en/create.yaml +11 -0
  16. data/data/butler/dialogs/en/delete.yaml +2 -0
  17. data/data/butler/dialogs/en/help.yaml +17 -0
  18. data/data/butler/dialogs/en/info.yaml +13 -0
  19. data/data/butler/dialogs/en/list.yaml +4 -0
  20. data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
  21. data/data/butler/dialogs/en/rename.yaml +3 -0
  22. data/data/butler/dialogs/en/start.yaml +3 -0
  23. data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
  24. data/data/butler/dialogs/en/uninstall.yaml +5 -0
  25. data/data/butler/dialogs/en/unknown_command.yaml +2 -0
  26. data/data/butler/dialogs/help.rb +11 -0
  27. data/data/butler/dialogs/info.rb +27 -0
  28. data/data/butler/dialogs/interactive.rb +1 -0
  29. data/data/butler/dialogs/list.rb +10 -0
  30. data/data/butler/dialogs/notyetimplemented.rb +1 -0
  31. data/data/butler/dialogs/rename.rb +4 -0
  32. data/data/butler/dialogs/selectbot.rb +2 -0
  33. data/data/butler/dialogs/start.rb +5 -0
  34. data/data/butler/dialogs/sync_plugins.rb +30 -0
  35. data/data/butler/dialogs/uninstall.rb +17 -0
  36. data/data/butler/dialogs/unknown_command.rb +1 -0
  37. data/data/butler/plugins/core/logout.rb +41 -0
  38. data/data/butler/plugins/core/plugins.rb +134 -0
  39. data/data/butler/plugins/core/privilege.rb +103 -0
  40. data/data/butler/plugins/core/user.rb +166 -0
  41. data/data/butler/plugins/dev/eval.rb +64 -0
  42. data/data/butler/plugins/dev/nometa.rb +14 -0
  43. data/data/butler/plugins/dev/onhandlers.rb +93 -0
  44. data/data/butler/plugins/dev/raw.rb +36 -0
  45. data/data/butler/plugins/dev/rawlog.rb +77 -0
  46. data/data/butler/plugins/games/eightball.rb +54 -0
  47. data/data/butler/plugins/games/mastermind.rb +174 -0
  48. data/data/butler/plugins/irc/action.rb +36 -0
  49. data/data/butler/plugins/irc/join.rb +38 -0
  50. data/data/butler/plugins/irc/notice.rb +36 -0
  51. data/data/butler/plugins/irc/part.rb +38 -0
  52. data/data/butler/plugins/irc/privmsg.rb +36 -0
  53. data/data/butler/plugins/irc/quit.rb +36 -0
  54. data/data/butler/plugins/operator/deop.rb +41 -0
  55. data/data/butler/plugins/operator/devoice.rb +41 -0
  56. data/data/butler/plugins/operator/limit.rb +47 -0
  57. data/data/butler/plugins/operator/op.rb +41 -0
  58. data/data/butler/plugins/operator/voice.rb +41 -0
  59. data/data/butler/plugins/public/help.rb +69 -0
  60. data/data/butler/plugins/public/login.rb +72 -0
  61. data/data/butler/plugins/public/usage.rb +49 -0
  62. data/data/butler/plugins/service/clones.rb +56 -0
  63. data/data/butler/plugins/service/define.rb +47 -0
  64. data/data/butler/plugins/service/log.rb +183 -0
  65. data/data/butler/plugins/service/svn.rb +91 -0
  66. data/data/butler/plugins/util/cycle.rb +98 -0
  67. data/data/butler/plugins/util/load.rb +41 -0
  68. data/data/butler/plugins/util/pong.rb +29 -0
  69. data/data/butler/strings/random/acknowledge.en.yaml +5 -0
  70. data/data/butler/strings/random/gratitude.en.yaml +3 -0
  71. data/data/butler/strings/random/hello.en.yaml +4 -0
  72. data/data/butler/strings/random/ignorance.en.yaml +7 -0
  73. data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
  74. data/data/butler/strings/random/insult.en.yaml +3 -0
  75. data/data/butler/strings/random/rejection.en.yaml +12 -0
  76. data/data/man/botcontrol.1 +17 -0
  77. data/lib/access.rb +187 -0
  78. data/lib/access/admin.rb +16 -0
  79. data/lib/access/privilege.rb +122 -0
  80. data/lib/access/role.rb +102 -0
  81. data/lib/access/savable.rb +18 -0
  82. data/lib/access/user.rb +180 -0
  83. data/lib/access/yamlbase.rb +126 -0
  84. data/lib/butler.rb +188 -0
  85. data/lib/butler/bot.rb +247 -0
  86. data/lib/butler/control.rb +93 -0
  87. data/lib/butler/dialog.rb +64 -0
  88. data/lib/butler/initialvalues.rb +40 -0
  89. data/lib/butler/irc/channel.rb +135 -0
  90. data/lib/butler/irc/channels.rb +96 -0
  91. data/lib/butler/irc/client.rb +351 -0
  92. data/lib/butler/irc/hostmask.rb +53 -0
  93. data/lib/butler/irc/message.rb +184 -0
  94. data/lib/butler/irc/parser.rb +125 -0
  95. data/lib/butler/irc/parser/commands.rb +83 -0
  96. data/lib/butler/irc/parser/generic.rb +343 -0
  97. data/lib/butler/irc/socket.rb +378 -0
  98. data/lib/butler/irc/string.rb +186 -0
  99. data/lib/butler/irc/topic.rb +15 -0
  100. data/lib/butler/irc/user.rb +265 -0
  101. data/lib/butler/irc/users.rb +112 -0
  102. data/lib/butler/plugin.rb +249 -0
  103. data/lib/butler/plugin/configproxy.rb +35 -0
  104. data/lib/butler/plugin/mapper.rb +85 -0
  105. data/lib/butler/plugin/matcher.rb +55 -0
  106. data/lib/butler/plugin/onhandlers.rb +70 -0
  107. data/lib/butler/plugin/trigger.rb +58 -0
  108. data/lib/butler/plugins.rb +147 -0
  109. data/lib/butler/version.rb +17 -0
  110. data/lib/cloptions.rb +217 -0
  111. data/lib/cloptions/adapters.rb +24 -0
  112. data/lib/cloptions/switch.rb +132 -0
  113. data/lib/configuration.rb +223 -0
  114. data/lib/dialogline.rb +296 -0
  115. data/lib/dialogline/localizations.rb +24 -0
  116. data/lib/durations.rb +57 -0
  117. data/lib/event.rb +295 -0
  118. data/lib/event/at.rb +64 -0
  119. data/lib/event/every.rb +56 -0
  120. data/lib/event/timed.rb +112 -0
  121. data/lib/installer.rb +75 -0
  122. data/lib/iterator.rb +34 -0
  123. data/lib/log.rb +68 -0
  124. data/lib/log/comfort.rb +85 -0
  125. data/lib/log/converter.rb +23 -0
  126. data/lib/log/entry.rb +152 -0
  127. data/lib/log/fakeio.rb +55 -0
  128. data/lib/log/file.rb +54 -0
  129. data/lib/log/filereader.rb +81 -0
  130. data/lib/log/forward.rb +49 -0
  131. data/lib/log/methods.rb +39 -0
  132. data/lib/log/nolog.rb +18 -0
  133. data/lib/log/splitter.rb +26 -0
  134. data/lib/ostructfixed.rb +26 -0
  135. data/lib/ruby/array/columnize.rb +38 -0
  136. data/lib/ruby/dir/mktree.rb +28 -0
  137. data/lib/ruby/enumerable/join.rb +13 -0
  138. data/lib/ruby/exception/detailed.rb +24 -0
  139. data/lib/ruby/file/append.rb +11 -0
  140. data/lib/ruby/file/write.rb +11 -0
  141. data/lib/ruby/hash/zip.rb +15 -0
  142. data/lib/ruby/kernel/bench.rb +15 -0
  143. data/lib/ruby/kernel/daemonize.rb +42 -0
  144. data/lib/ruby/kernel/non_verbose.rb +17 -0
  145. data/lib/ruby/kernel/safe_fork.rb +18 -0
  146. data/lib/ruby/range/stepped.rb +11 -0
  147. data/lib/ruby/string/arguments.rb +72 -0
  148. data/lib/ruby/string/chunks.rb +15 -0
  149. data/lib/ruby/string/post_arguments.rb +44 -0
  150. data/lib/ruby/string/unescaped.rb +17 -0
  151. data/lib/scheduler.rb +164 -0
  152. data/lib/scriptfile.rb +101 -0
  153. data/lib/templater.rb +86 -0
  154. data/test/cloptions.rb +134 -0
  155. data/test/cv.rb +28 -0
  156. data/test/irc/client.rb +85 -0
  157. data/test/irc/client_login.txt +53 -0
  158. data/test/irc/client_subscribe.txt +8 -0
  159. data/test/irc/message.rb +30 -0
  160. data/test/irc/messages.txt +64 -0
  161. data/test/irc/parser.rb +13 -0
  162. data/test/irc/profile_parser.rb +12 -0
  163. data/test/irc/users.rb +28 -0
  164. metadata +256 -0
@@ -0,0 +1,247 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'butler'
10
+ require 'butler/irc/client'
11
+ require 'configuration'
12
+ require 'log'
13
+ require 'butler/plugins'
14
+ require 'ruby/string/arguments'
15
+ require 'ruby/string/post_arguments'
16
+ require 'scheduler'
17
+ require 'set'
18
+ require 'thread'
19
+
20
+
21
+
22
+ # FIXME: credits @ daemonize lib
23
+ class Butler
24
+ class Bot < IRC::Client
25
+ include Log::Comfort
26
+
27
+ attr_reader :access
28
+ attr_reader :base
29
+ attr_reader :config
30
+ attr_reader :log_device
31
+ attr_reader :path
32
+ attr_reader :plugins
33
+ attr_reader :scheduler
34
+
35
+ # FIXME, raise if selftest fails
36
+ def initialize(path, name, opts={})
37
+ path ||= Butler.path
38
+ @irc = nil # early inspects
39
+ @name = name
40
+ @base = "#{path.bots}/#{name}"
41
+ @path = OpenStruct.new(
42
+ :access => @base+'/access',
43
+ :base => @base,
44
+ :config => @base+'/config',
45
+ :log => @base+'/log',
46
+ :plugins => @base+'/plugins',
47
+ :strings => @base+'/strings'
48
+ )
49
+ @log_device = $stderr
50
+ @config = Configuration.new(@base+'/config')
51
+ @scheduler = Scheduler.new
52
+ @access = Access.new(
53
+ Access::YAMLBase.new(Access::User::Base, @base+'/access/user'),
54
+ Access::YAMLBase.new(Access::Role::Base, @base+'/access/role'),
55
+ Access::YAMLBase.new(Access::Privilege::Base, @base+'/access/privilege')
56
+ #:channel => Access::YAMLBase.new("#{TestDir}/channel", Access::Location)
57
+ )
58
+ @on_disconnect = nil
59
+ @on_reconnect = nil
60
+ @reconnect = [
61
+ opts.delete(:reconnect_delay) || 60,
62
+ opts.delete(:reconnect_tries) || -1
63
+ ]
64
+
65
+ super(@config["connections.main.server"], {
66
+ :port => @config["connections.main.port"],
67
+ :host => @config["connections.main.host"]
68
+ }.merge(opts))
69
+
70
+ # { lang => { trigger => SortedSet[ *commands ] } }
71
+ @commands = {}
72
+ @plugins = Plugins.new(self, @base+'/plugins')
73
+
74
+ subscribe(:PRIVMSG, -10, &method(:invoke_commands))
75
+ subscribe(:NOTICE, -10, &method(:invoke_commands))
76
+
77
+ selftest
78
+ end
79
+
80
+ def invoke_commands(listener, message)
81
+ return unless ((sequence = invocation(message.text)) || message.realm == :private)
82
+ message.invocation = sequence || ""
83
+ message.language = "en"
84
+ trigger = message.arguments.first
85
+ access = message.from && message.from.access.method(:authorized?)
86
+ return unless access and trigger
87
+
88
+ trigger = trigger.downcase
89
+ commands = []
90
+ if @commands[message.language] && @commands[message.language][trigger] then
91
+ commands.concat(@commands[message.language][trigger].to_a)
92
+ end
93
+ if message.language != "en" && @commands["en"] && @commands["en"][trigger] then
94
+ commands.concat(@commands["en"][trigger].to_a)
95
+ end
96
+
97
+ commands.each { |command|
98
+ if args = (command.invoked_by?(message)) then
99
+ if !command.authorization || access.call(command.authorization) then
100
+ Thread.new {
101
+ begin
102
+ command.call(message, *args)
103
+ rescue Exception => e
104
+ exception(e)
105
+ end
106
+ }
107
+ break if command.abort_invocations?
108
+ else
109
+ info("#{message.from} (#{message.from.access.id}) had no authorization for 'plugin/#{command.plugin.base}'")
110
+ end
111
+ end
112
+ }
113
+ end
114
+
115
+ def add_command(command)
116
+ @commands[command.language] ||= {}
117
+ @commands[command.language][command.trigger] ||= SortedSet.new
118
+ @commands[command.language][command.trigger].add(command)
119
+ end
120
+
121
+ def delete_command(command)
122
+ @commands[command.language][command.trigger].delete(command)
123
+ if @commands[command.language][command.trigger].empty? then
124
+ @commands[command.language].delete(command.trigger)
125
+ @commands.delete(command.language) if @commands[command.language].empty?
126
+ end
127
+ end
128
+
129
+ # returns the rest of the sequence if it was an invocation, nil else
130
+ def invocation(sequence)
131
+ @myself && sequence[/^(?:!?#{@myself.nick}[:;,])\s+/i]
132
+ end
133
+
134
+ # runs a diagnose, will collect all wrongs and raise if it finds any
135
+ def selftest
136
+
137
+ end
138
+
139
+ def login
140
+ @access.default_user = @access["default_user"]
141
+ @access.default_user.login
142
+
143
+ nick = @config["connections.main.nick"]
144
+ pass = @config["connections.main.password"]
145
+ super(
146
+ nick,
147
+ @config["connections.main.user"],
148
+ @config["connections.main.real"]
149
+ )
150
+ info("Logged in")
151
+ # FIXME, use #same_nick?
152
+ if pass && @myself.nick.downcase != nick.downcase then
153
+ @irc.ghost(nick, pass)
154
+ sleep(1) # FIXME, do a wait_for or similar
155
+ @irc.nick(nick)
156
+ end
157
+ @irc.identify(pass) if pass
158
+ join(*@config["connections.main.channels"])
159
+ end
160
+
161
+ def on_disconnect(reason)
162
+ return if reason == :quit
163
+ unless @reconnect[1].zero? then
164
+ @reconnect[1] -= 1
165
+ sleep(@reconnect[0])
166
+ login
167
+ end
168
+ end
169
+
170
+
171
+ def output_to_logfiles
172
+ # Errors still go to $stderr, $stdin handles puts as "info" level $stderr prints
173
+ @log_device = Log.file(@path.log+'/error.log')
174
+ $stdout = Log.forward(@log_device, :warn)
175
+ $stderr = Log.forward(@log_device, :info)
176
+ end
177
+
178
+ def inspect # :nodoc:
179
+ "#<%s:0x%08x %s (in %s) irc=%s>" % [
180
+ self.class,
181
+ object_id << 1,
182
+ @name,
183
+ @base,
184
+ @irc.inspect
185
+ ]
186
+ end
187
+ end # Bot
188
+
189
+ class IRC::Users
190
+ attr_reader :client
191
+ end
192
+
193
+ class IRC::User
194
+ attr_accessor :access
195
+
196
+ def authorized?(*args)
197
+ @access.authorized?(*args)
198
+ end
199
+
200
+ alias butler_initialize initialize unless method_defined? :butler_initialize
201
+ def initialize(users, *args, &block)
202
+ butler_initialize(users, *args, &block)
203
+ @access = users.client.access.default_user
204
+ end
205
+ end # IRC::User
206
+
207
+ class IRC::Message
208
+ # the language of this message, as determined by butler
209
+ attr_accessor :language
210
+
211
+ # the invocation-sequence (e.g. "butler, "), nil if none was used (e.g. in
212
+ # private messages)
213
+ attr_accessor :invocation
214
+
215
+ alias butler_initialize initialize unless method_defined? :butler_initialize
216
+ def initialize(*args, &block)
217
+ butler_initialize(*args, &block)
218
+ @language = nil
219
+ @invocation = nil
220
+ @arguments = nil
221
+ @post_arguments = nil
222
+ end
223
+
224
+ # parses a given string into argument-tokens. a token is either a word or a quoted string. escapes are respected.
225
+ # e.g. 'Hallo "this is token2" "and this \"token\" is token3"'
226
+ # would be parsed into: ["hallo", "this is token2", "and this \"token\" is token3"]
227
+ def arguments
228
+ return [] unless text # only messages with text can be tokenized to parameters
229
+ @arguments ||= begin # don't do double-work
230
+ text[@invocation.length..-1].arguments
231
+ rescue String::SingleQuoteException => ex
232
+ ex.pre+[ex.post.join(" ")]
233
+ end
234
+ end
235
+
236
+ # FIXME due to it's current working, post_arguments will strip formatting
237
+ # Returns the rest of message text after argument
238
+ # == Synopsis
239
+ # "foo bar baz".post_arguments[0] # => "foo bar baz"
240
+ # "foo bar baz".post_arguments[1] # => "bar baz"
241
+ # "foo bar baz".post_arguments[2] # => "baz"
242
+ def post_arguments
243
+ return [] unless text # only messages with text can be tokenized to parameters
244
+ @post_arguments ||= text[@invocation.length..-1].post_arguments # don't do double-work
245
+ end
246
+ end # IRC::Message
247
+ end # Butler
@@ -0,0 +1,93 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'ruby/file/write'
10
+
11
+
12
+
13
+ class Butler
14
+ IsGem = true # REPLACE: IsGem = false
15
+
16
+ # this module provides services to botcontrol
17
+ # for gems, this module uses the Gem module to get information on the where-
18
+ # abouts of data. If installed using rake from tarball, it is hard-coded.
19
+ class Control
20
+ attr_reader :dialog
21
+ attr_reader :config
22
+ attr_reader :path
23
+
24
+ def initialize
25
+ @path = OpenStruct.new({
26
+ :template => Gem.datadir("butler")+'/config_template.yaml', # DELETE
27
+ :config => Gem.datadir("butler")+'/config.yaml', # REPLACE: :config => %CONFIG_DIR%+'/config.yaml',
28
+ :dialogs => Gem.datadir("butler")+'/dialogs', # REPLACE: :dialogs => %DIALOGS_DIR%,
29
+ :plugins => Gem.datadir("butler")+'/plugins', # REPLACE: :plugins => %PLUGINS_DIR%,
30
+ :strings => Gem.datadir("butler")+'/strings', # REPLACE: :strings => %STRINGS_DIR%,
31
+ :man => Gem.datadir("butler")+'/man1', # DELETE
32
+ })
33
+ File.write(@path.config, File.read(@path.template)) unless File.exist?(@path.config)
34
+ @config = OpenStruct.new(YAML.load_file(@path.config))
35
+ @dialog = DialogLine.new(@path.dialogs, @config.language)
36
+ end
37
+
38
+ def user(name=nil)
39
+ # use Etc.getlogin?
40
+ name || ENV['SUDO_USER'] || ENV['USER'] || @dialog.discuss("username", false)[:name]
41
+ end
42
+
43
+ def configured?(user=nil)
44
+ path = @config.users[user(user)]
45
+ path && File.exist?(path)
46
+ end
47
+
48
+ def configure_user(user=nil)
49
+ raise Errno::EACCES unless File.writable?(@path.config)
50
+ user ||= user()
51
+ installer = Installer.new("butler")
52
+ user_config = @dialog.discuss(:botcontrol, false,
53
+ :installer => installer,
54
+ :user => user,
55
+ :path => path
56
+ )
57
+ path = @config.users[user] = user_config.delete(:config)+"/config.yaml"
58
+ user_config[:language] = "en" # FIXME multilingualize
59
+ update_config
60
+ FileUtils.mkdir_p(File.dirname(path))
61
+ FileUtils.chown(user, nil, File.dirname(path))
62
+ File.write(path, user_config.to_yaml)
63
+ FileUtils.chown(user, nil, path)
64
+ OpenStruct.new(@config.users[user])
65
+ end
66
+
67
+ def update_config
68
+ File.write(@path.config, @config.to_hash.to_yaml)
69
+ end
70
+
71
+ def user_config(user=nil)
72
+ path = @config.users[user(user)]
73
+ user_config = OpenStruct.new(YAML.load_file(path))
74
+ user_config.plugin_repository = @path.plugins
75
+ user_config
76
+ end
77
+
78
+ def butler_path(user=nil)
79
+ path = @config.users[user(user)]
80
+ user_config = OpenStruct.new(YAML.load_file(path))
81
+ user_config.plugin_repository = @path.plugins
82
+ user_config
83
+ end
84
+
85
+ def discuss(dir, lang=nil, variables={}, &block)
86
+ @dialog.discuss(dir, lang, variables.merge(:botcontrol => self), &block)
87
+ end
88
+
89
+ def language
90
+ "en"
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,64 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'durations'
10
+
11
+
12
+
13
+ class Butler
14
+ # A message can be converted to a dialog, this will open a query to the
15
+ # originator of the query and intercept every message there
16
+ class Dialog
17
+ @timeout = 5.minutes, :timeout
18
+ @reminder = 3.minutes, :reminder
19
+
20
+ class <<self
21
+ def timeout(after, string)
22
+ @timeout = after, string
23
+ end
24
+
25
+ def reminder(after, string)
26
+ @reminder = after, string
27
+ end
28
+ end
29
+
30
+ def initialize(plugin)
31
+ @plugin = plugin
32
+ @plugin.butler.subscribe(:PRIVMSG, 0, true) { |listener, message|
33
+ if message.private? then
34
+ postpone
35
+ dialog(listener, message)
36
+ :block
37
+ else
38
+ :pass
39
+ end
40
+ }
41
+ end
42
+
43
+ def postpone
44
+ @reminder.postpone
45
+ @timeout.postpone
46
+ end
47
+
48
+ def dialog(listener, message)
49
+
50
+ end
51
+
52
+ def timeout(timer, string)
53
+ end
54
+
55
+ def localize(*args)
56
+ @plugin.localize(*args)
57
+ end
58
+ alias _ localize
59
+
60
+ def method_missing(*args)
61
+ @plugin.__send__(*args)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,40 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ class Butler
10
+ Structure = %w[
11
+ BOTPATH
12
+ BOTPATH/access
13
+ BOTPATH/access/privilege
14
+ BOTPATH/access/role
15
+ BOTPATH/access/user
16
+ BOTPATH/log
17
+ BOTPATH/plugins
18
+ BOTPATH/strings
19
+ ].map { |e| [e, 0755] }
20
+ # first one is the base-directory
21
+ ConfigurationStructure = %w[
22
+ BOTPATH/config
23
+ connections
24
+ channels
25
+ plugin
26
+ ]
27
+ EmptyConfig = {
28
+ 'remote' => {
29
+ 'host' => 'localhost',
30
+ 'port' => 7666,
31
+ 'active' => false,
32
+ },
33
+ 'logging' => {
34
+ },
35
+ 'owner' => {
36
+ 'username' => nil, # no owner
37
+ 'contact' => 'unknown',
38
+ },
39
+ }
40
+ end
@@ -0,0 +1,135 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'butler/irc/topic'
10
+ require 'thread'
11
+
12
+
13
+
14
+ class Butler
15
+ module IRC
16
+ class Channel
17
+ include Comparable
18
+ include Enumerable
19
+
20
+ #the name of the channel (immutable)
21
+ attr_reader :name
22
+ #the current topic of the channel
23
+ attr_reader :topic
24
+
25
+ # the value used for comparison/sorting
26
+ attr_reader :compare
27
+
28
+ # :nodoc:
29
+ attr_reader :hash
30
+
31
+ # Create a Butler::IRC::Channel-object.
32
+ # Use Channel.create(<channelname>) if you can.
33
+ def initialize(all_users, name, topic=nil, topic_set_by=nil, topic_set_at=nil)
34
+ name = name.to_str.strip_user_prefixes
35
+ raise "Invalid channelname '#{name}'" unless name.valid_channelname?
36
+ @all_users = all_users
37
+ @name = name.freeze
38
+ @compare = name.downcase.freeze
39
+ @hash = @compare.hash
40
+ @users = {}
41
+ @topic = Topic.new(topic || "", topic_set_by, topic_set_at)
42
+ @lock = Mutex.new
43
+ end
44
+
45
+ # Looks up if there are clones in the list of users
46
+ # min defines how many users need to have the same host (+user if strong)
47
+ # to appear in the list.
48
+ def clones(strong=false, min=2)
49
+ sieve = Hash.new { |h,k| h[k] = [] }
50
+ if strong then
51
+ users.each { |user|
52
+ sieve["#{user.user}@#{user.host}"] << user if (user.user && user.host)
53
+ }
54
+ else
55
+ users = users()
56
+ users.each { |user|
57
+ sieve[user.host] << user if user.host
58
+ }
59
+ end
60
+ sieve.select { |host, users| users.length >= min }
61
+ end
62
+
63
+ #List of users (Butler::IRC::User objects) in this channel
64
+ def users
65
+ @all_users.map_nicks(*@users.keys)
66
+ end
67
+
68
+ # List of normalized (lowercased and flag-stripped) nicknames in this channel
69
+ def nicks
70
+ @users.keys
71
+ end
72
+
73
+ # Iterate over all users in this channel
74
+ def each
75
+ @users.each_value { |user| yield @all_users[user] }
76
+ end
77
+
78
+ # Returns amount of users
79
+ def length
80
+ @users.length
81
+ end
82
+
83
+ # Comparison based on lowercase channelname
84
+ def ==(other)
85
+ @compare == other.to_str.downcase
86
+ end
87
+
88
+ # Comparison based on lowercase channelname, -1, 0 or 1
89
+ def <=>(other)
90
+ @compare <=> other.to_str.downcase
91
+ end
92
+
93
+ # Add a user to the channel, see Butler::IRC::CHANNEL_SIGNALS for possible
94
+ # reasons (should only be used by IRC::Parser)
95
+ def add_user(user, reason) #:nodoc:
96
+ @users[user.to_str.downcase] ||= 0
97
+ end
98
+
99
+ # Get user by nick if or nil if that user is not in this channel
100
+ def [](nick)
101
+ nick = nick.to_str.downcase
102
+ @users.has_key?(nick) ? @all_users[nick] : nil
103
+ end
104
+
105
+ # Test if user with nick is in this channel (also works with IRC::User objects)
106
+ def include?(nick)
107
+ @users.has_key?(nick.to_str.downcase)
108
+ end
109
+
110
+ # Remove a user from the channel, see Butler::IRC::CHANNEL_SIGNALS for
111
+ # possible reasons (should only be used by Butler::IRC::Parser)
112
+ def delete_user(user, reason) #:nodoc:
113
+ @users.delete(user.to_str.downcase)
114
+ end
115
+
116
+ def inspect # :nodoc:
117
+ "#<%s:0x%x %s (%d users)" % [self.class, object_id, @name, length]
118
+ end
119
+
120
+ # Returns the (frozen!) name of the channel
121
+ def to_s
122
+ @name
123
+ end
124
+
125
+ # Returns the normalized name of the channel
126
+ def to_str #:nodoc:
127
+ @compare
128
+ end
129
+
130
+ def eql?(other) #:nodoc:
131
+ self.class == other.class && @compare == other.compare
132
+ end
133
+ end
134
+ end
135
+ end