butler 1.8.0

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