4 # :title: rbot config management from IRC
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
8 class ConfigModule < CoreBotModule
11 if $version_timestamp.to_i > 0
12 ago = _(" [%{secs} ago]") % {
13 :secs => Utils.secs_to_string(Time.now.to_i - $version_timestamp.to_i)
18 _("I'm a v. %{version}%{ago} rubybot%{copyright}%{url}") % {
21 :copyright => ", #{Irc::Bot::COPYRIGHT_NOTICE}",
22 :url => " - #{Irc::Bot::SOURCE_URL}"
30 def handle_list(m, params)
33 @bot.config.items.each_key do |key|
34 mod, name = key.to_s.split('.')
35 next unless mod == params[:module]
36 modules.push key unless modules.include?(name)
39 m.reply _("no such module %{module}") % {:module => params[:module]}
41 m.reply modules.join(", ")
44 @bot.config.items.each_key do |key|
45 name = key.to_s.split('.').first
46 modules.push name unless modules.include?(name)
48 m.reply "modules: " + modules.join(", ")
52 def handle_get(m, params)
53 key = params[:key].to_s.intern
54 unless @bot.config.items.has_key?(key)
55 m.reply _("no such config key %{key}") % {:key => key}
58 return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)
59 value = @bot.config.items[key].to_s
60 m.reply "#{key}: #{value}"
63 def handle_desc(m, params)
64 key = params[:key].to_s.intern
65 unless @bot.config.items.has_key?(key)
66 m.reply _("no such config key %{key}") % {:key => key}
68 m.reply "#{key}: #{@bot.config.items[key].desc}"
71 def handle_search(m, params)
72 rx = Regexp.new(params[:rx].to_s, true)
74 @bot.config.items.each do |k, v|
75 cfs << [Bold + k.to_s + Bold, v.desc] if k.to_s.match(rx) or (v.desc.match(rx) rescue false)
78 m.reply _("no config key found matching %{r}") % { :r => params[:rx].to_s}
80 m.reply _("possible keys: %{kl}") % { :kl => cfs.map { |c| c.first}.sort.join(', ') } if cfs.length > 1
81 m.reply cfs.map { |c| c.join(': ') }.join("\n")
85 def handle_unset(m, params)
86 key = params[:key].to_s.intern
87 unless @bot.config.items.has_key?(key)
88 m.reply _("no such config key %{key}") % {:key => key}
90 return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)
91 @bot.config.items[key].unset
93 m.reply _("this config change will take effect on the next restart") if @bot.config.items[key].requires_restart
94 m.reply _("this config change will take effect on the next rescan") if @bot.config.items[key].requires_rescan
97 def handle_set(m, params)
98 key = params[:key].to_s.intern
99 value = params[:value].join(" ")
100 unless @bot.config.items.has_key?(key)
101 m.reply _("no such config key %{key}") % {:key => key} unless params[:silent]
104 return false if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)
106 @bot.config.items[key].set_string(value)
107 rescue ArgumentError => e
108 m.reply _("failed to set %{key}: %{error}") % {:key => key, :error => e.message} unless params[:silent]
111 if @bot.config.items[key].requires_restart
112 m.reply _("this config change will take effect on the next restart") unless params[:silent]
114 elsif @bot.config.items[key].requires_rescan
115 m.reply _("this config change will take effect on the next rescan") unless params[:silent]
118 m.okay unless params[:silent]
123 def handle_add(m, params)
124 key = params[:key].to_s.intern
125 values = params[:value].to_s.split(/,\s+/)
126 unless @bot.config.items.has_key?(key)
127 m.reply _("no such config key %{key}") % {:key => key}
130 unless @bot.config.items[key].kind_of?(Config::ArrayValue)
131 m.reply _("config key %{key} is not an array") % {:key => key}
134 return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)
135 values.each do |value|
137 @bot.config.items[key].add(value)
138 rescue ArgumentError => e
139 m.reply _("failed to add %{value} to %{key}: %{error}") % {:value => value, :key => key, :error => e.message}
143 handle_get(m,{:key => key})
144 m.reply _("this config change will take effect on the next restart") if @bot.config.items[key].requires_restart
145 m.reply _("this config change will take effect on the next rescan") if @bot.config.items[key].requires_rescan
148 def handle_rm(m, params)
149 key = params[:key].to_s.intern
150 values = params[:value].to_s.split(/,\s+/)
151 unless @bot.config.items.has_key?(key)
152 m.reply _("no such config key %{key}") % {:key => key}
155 unless @bot.config.items[key].kind_of?(Config::ArrayValue)
156 m.reply _("config key %{key} is not an array") % {:key => key}
159 return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)
160 values.each do |value|
162 @bot.config.items[key].rm(value)
163 rescue ArgumentError => e
164 m.reply _("failed to remove %{value} from %{key}: %{error}") % {:value => value, :key => key, :error => e.message}
168 handle_get(m,{:key => key})
169 m.reply _("this config change will take effect on the next restart") if @bot.config.items[key].requires_restart
170 m.reply _("this config change will take effect on the next rescan") if @bot.config.items[key].requires_rescan
173 def bot_save(m, param)
178 def bot_rescan(m, param)
180 name = param[:botmodule]
181 if not @bot.plugins.has_key? name
182 m.reply _("botmodule not found")
185 botmodule = @bot.plugins[name]
186 m.reply _("botmodule %s... saving... rescanning...") % [name]
189 m.reply _("saving... rescanning...")
192 @bot.rescan(botmodule)
193 m.reply _("done. %{plugin_status}") % {
194 :plugin_status => @bot.plugins.status(true)}
195 failure = @bot.plugins.botmodule_failure(name) if botmodule
197 m.reply _("plugin failed to load, %{failure}") % {
202 def bot_nick(m, param)
203 @bot.nickchg(param[:nick])
204 @bot.wanted_nick = param[:nick]
207 def bot_status(m, param)
211 # TODO is this one of the methods that disappeared when the bot was moved
212 # from the single-file to the multi-file registry?
214 # def bot_reg_stat(m, param)
215 # m.reply @registry.stat.inspect
218 def bot_version(m, param)
219 m.reply version_string
223 who = m.private? ? "me" : m.target
226 m.ctcp_reply version_string
228 m.ctcp_reply Irc::Bot::SOURCE_URL
232 def handle_help(m, params)
233 m.reply help(params[:topic])
236 def help(plugin, topic="")
241 _("config list => list configuration modules, config list <module> => list configuration keys for module <module>")
243 _("config get <key> => get configuration value for key <key>")
245 _("reset key <key> to the default")
247 _("config set <key> <value> => set configuration value for key <key> to <value>")
249 _("config desc <key> => describe what key <key> configures")
251 _("config add <values> to <key> => add values <values> to key <key> if <key> is an array")
253 _("config rm <value> from <key> => remove value <value> from key <key> if <key> is an array")
255 _("config module - bot configuration. usage: list, desc, get, set, unset, add, rm")
257 # "no help for config #{topic}"
260 _("nick <newnick> => change the bot nick to <newnick>, if possible")
262 _("status => display some information on the bot's status")
264 _("save => save current dynamic data and configuration")
266 _("rescan [<botmodule>] => reload specified or all botmodules and static facts")
268 _("reload [<botmodule>] => reload specified or all botmodules and static facts")
270 _("version => describes software version")
272 _("config-related tasks: config, save, rescan(/reload), version, nick, status")
278 conf = ConfigModule.new
280 conf.map 'config list :module',
281 :action => 'handle_list',
282 :defaults => {:module => false},
284 # TODO this one is presently a security risk, since the bot
285 # stores the master password in the config. Do we need auth levels
286 # on the Bot::Config keys too?
287 conf.map 'config get :key',
288 :action => 'handle_get',
290 conf.map 'config desc :key',
291 :action => 'handle_desc',
293 conf.map 'config describe :key',
294 :action => 'handle_desc',
295 :auth_path => 'show::desc!'
296 conf.map 'config search *rx',
297 :action => 'handle_search',
301 :action => 'bot_save'
302 conf.map "rescan [:botmodule]",
303 :action => 'bot_rescan'
304 conf.map "reload [:botmodule]",
305 :action => 'bot_rescan'
306 conf.map "nick :nick",
307 :action => 'bot_nick'
309 :action => 'bot_status',
310 :auth_path => 'show::status'
313 # conf.map "registry stats",
314 # :action => 'bot_reg_stat',
315 # :auth_path => '!config::status'
317 :action => 'bot_version',
318 :auth_path => 'show::status'
320 conf.map 'config set :key *value',
321 :action => 'handle_set',
323 conf.map 'config add *value to :key',
324 :action => 'handle_add',
326 conf.map 'config rm *value from :key',
327 :action => 'handle_rm',
329 conf.map 'config remove *value from :key',
330 :action => 'handle_rm',
332 conf.map 'config del *value from :key',
333 :action => 'handle_rm',
335 conf.map 'config delete *value from :key',
336 :action => 'handle_rm',
338 conf.map 'config unset :key',
339 :action => 'handle_unset',
341 conf.map 'config reset :key',
342 :action => 'handle_unset',
345 conf.map 'config help :topic',
346 :action => 'handle_help',
347 :defaults => {:topic => false},
348 :auth_path => '!help!'
350 conf.default_auth('*', false)
351 conf.default_auth('show', true)
352 conf.default_auth('show::get', false)
353 # TODO these shouldn't be set here, we need a way to let the default
354 # permission be specified together with the ConfigValue
355 conf.default_auth('key', true)
356 conf.default_auth('key::auth::password', false)