introduce reload alias for rescan
[rbot] / lib / rbot / core / config.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: rbot config management from IRC
5 #
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
7
8 class ConfigModule < CoreBotModule
9
10   def version_string
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)
14       }
15     else
16       ago = ''
17     end
18     _("I'm a v. %{version}%{ago} rubybot%{copyright}%{url}") % {
19       :version => $version,
20       :ago => ago,
21       :copyright => ", #{Irc::Bot::COPYRIGHT_NOTICE}",
22       :url => " - #{Irc::Bot::SOURCE_URL}"
23     }
24   end
25
26   def save
27     @bot.config.save
28   end
29
30   def handle_list(m, params)
31     modules = []
32     if params[:module]
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)
37       end
38       if modules.empty?
39         m.reply _("no such module %{module}") % {:module => params[:module]}
40       else
41         m.reply modules.join(", ")
42       end
43     else
44       @bot.config.items.each_key do |key|
45         name = key.to_s.split('.').first
46         modules.push name unless modules.include?(name)
47       end
48       m.reply "modules: " + modules.join(", ")
49     end
50   end
51
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}
56       return
57     end
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}"
61   end
62
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}
67     end
68     m.reply "#{key}: #{@bot.config.items[key].desc}"
69   end
70
71   def handle_search(m, params)
72     rx = Regexp.new(params[:rx].to_s, true)
73     cfs = []
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)
76     end
77     if cfs.empty?
78       m.reply _("no config key found matching %{r}") % { :r => params[:rx].to_s}
79     else
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")
82     end
83   end
84
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}
89     end
90     return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)
91     @bot.config.items[key].unset
92     handle_get(m, params)
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
95   end
96
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]
102       return false
103     end
104     return false if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)
105     begin
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]
109       return false
110     end
111     if @bot.config.items[key].requires_restart
112       m.reply _("this config change will take effect on the next restart") unless params[:silent]
113       return :restart
114     elsif @bot.config.items[key].requires_rescan
115       m.reply _("this config change will take effect on the next rescan") unless params[:silent]
116       return :rescan
117     else
118       m.okay unless params[:silent]
119       return true
120     end
121   end
122
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}
128       return
129     end
130     unless @bot.config.items[key].kind_of?(Config::ArrayValue)
131       m.reply _("config key %{key} is not an array") % {:key => key}
132       return
133     end
134     return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)
135     values.each do |value|
136       begin
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}
140         next
141       end
142     end
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
146   end
147
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}
153       return
154     end
155     unless @bot.config.items[key].kind_of?(Config::ArrayValue)
156       m.reply _("config key %{key} is not an array") % {:key => key}
157       return
158     end
159     return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)
160     values.each do |value|
161       begin
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}
165         next
166       end
167     end
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
171   end
172
173   def bot_save(m, param)
174     @bot.save
175     m.okay
176   end
177
178   def bot_rescan(m, param)
179     if param[:botmodule]
180       name = param[:botmodule]
181       if not @bot.plugins.has_key? name
182         m.reply _("botmodule not found")
183         return # error
184       else
185         botmodule = @bot.plugins[name]
186         m.reply _("botmodule %s... saving... rescanning...") % [name]
187       end
188     else
189       m.reply _("saving... rescanning...")
190     end
191
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
196     if failure
197       m.reply _("plugin failed to load, %{failure}") % {
198         :failure => failure}
199     end
200   end
201
202   def bot_nick(m, param)
203     @bot.nickchg(param[:nick])
204     @bot.wanted_nick = param[:nick]
205   end
206
207   def bot_status(m, param)
208     m.reply @bot.status
209   end
210
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?
213   #
214   #  def bot_reg_stat(m, param)
215   #    m.reply @registry.stat.inspect
216   #  end
217
218   def bot_version(m, param)
219     m.reply version_string
220   end
221
222   def ctcp_listen(m)
223     who = m.private? ? "me" : m.target
224     case m.ctcp.intern
225     when :VERSION
226       m.ctcp_reply version_string
227     when :SOURCE
228       m.ctcp_reply Irc::Bot::SOURCE_URL
229     end
230   end
231
232   def handle_help(m, params)
233     m.reply help(params[:topic])
234   end
235
236   def help(plugin, topic="")
237     case plugin
238     when "config"
239       case topic
240       when "list"
241       _("config list => list configuration modules, config list <module> => list configuration keys for module <module>")
242       when "get"
243       _("config get <key> => get configuration value for key <key>")
244       when "unset"
245       _("reset key <key> to the default")
246       when "set"
247       _("config set <key> <value> => set configuration value for key <key> to <value>")
248       when "desc"
249       _("config desc <key> => describe what key <key> configures")
250       when "add"
251       _("config add <values> to <key> => add values <values> to key <key> if <key> is an array")
252       when "rm"
253       _("config rm <value> from <key> => remove value <value> from key <key> if <key> is an array")
254       else
255       _("config module - bot configuration. usage: list, desc, get, set, unset, add, rm")
256       # else
257       #   "no help for config #{topic}"
258       end
259     when "nick"
260       _("nick <newnick> => change the bot nick to <newnick>, if possible")
261     when "status"
262       _("status => display some information on the bot's status")
263     when "save"
264       _("save => save current dynamic data and configuration")
265     when "rescan"
266       _("rescan [<botmodule>] => reload specified or all botmodules and static facts")
267     when "reload"
268       _("reload [<botmodule>] => reload specified or all botmodules and static facts")
269     when "version"
270       _("version => describes software version")
271     else
272       _("config-related tasks: config, save, rescan(/reload), version, nick, status")
273     end
274   end
275
276 end
277
278 conf = ConfigModule.new
279
280 conf.map 'config list :module',
281   :action => 'handle_list',
282   :defaults => {:module => false},
283   :auth_path => 'show'
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',
289   :auth_path => 'show'
290 conf.map 'config desc :key',
291   :action => 'handle_desc',
292   :auth_path => 'show'
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',
298   :auth_path => 'show'
299
300 conf.map "save",
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'
308 conf.map "status",
309   :action => 'bot_status',
310   :auth_path => 'show::status'
311 # TODO see above
312 #
313 # conf.map "registry stats",
314 #   :action => 'bot_reg_stat',
315 #   :auth_path => '!config::status'
316 conf.map "version",
317   :action => 'bot_version',
318   :auth_path => 'show::status'
319
320 conf.map 'config set :key *value',
321   :action => 'handle_set',
322   :auth_path => 'edit'
323 conf.map 'config add *value to :key',
324   :action => 'handle_add',
325   :auth_path => 'edit'
326 conf.map 'config rm *value from :key',
327   :action => 'handle_rm',
328   :auth_path => 'edit'
329 conf.map 'config remove *value from :key',
330   :action => 'handle_rm',
331   :auth_path => 'edit'
332 conf.map 'config del *value from :key',
333   :action => 'handle_rm',
334   :auth_path => 'edit'
335 conf.map 'config delete *value from :key',
336   :action => 'handle_rm',
337   :auth_path => 'edit'
338 conf.map 'config unset :key',
339   :action => 'handle_unset',
340   :auth_path => 'edit'
341 conf.map 'config reset :key',
342   :action => 'handle_unset',
343   :auth_path => 'edit'
344
345 conf.map 'config help :topic',
346   :action => 'handle_help',
347   :defaults => {:topic => false},
348   :auth_path => '!help!'
349
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)
357