config core module: default command auth fixes
[rbot] / lib / rbot / core / config.rb
1 #-- vim:sw=2:et\r
2 #++\r
3 #\r
4 # :title: rbot config management from IRC\r
5 #\r
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>\r
7 # Copyright:: (C) 2006,2007 Giuseppe Bilotta\r
8 # License:: GPL v2\r
9 \r
10 class ConfigModule < CoreBotModule\r
11 \r
12   def version_string\r
13     _("I'm a v. %{version} rubybot%{copyright}%{url}") % {\r
14       :version => $version,\r
15       :copyright => ", #{Irc::Bot::COPYRIGHT_NOTICE}",\r
16       :url => " - #{Irc::Bot::SOURCE_URL}"\r
17     }\r
18   end\r
19 \r
20   def save\r
21     @bot.config.save\r
22   end\r
23 \r
24   def handle_list(m, params)\r
25     modules = []\r
26     if params[:module]\r
27       @bot.config.items.each_key do |key|\r
28         mod, name = key.to_s.split('.')\r
29         next unless mod == params[:module]\r
30         modules.push key unless modules.include?(name)\r
31       end\r
32       if modules.empty?\r
33         m.reply _("no such module %{module}") % {:module => params[:module]}\r
34       else\r
35         m.reply modules.join(", ")\r
36       end\r
37     else\r
38       @bot.config.items.each_key do |key|\r
39         name = key.to_s.split('.').first\r
40         modules.push name unless modules.include?(name)\r
41       end\r
42       m.reply "modules: " + modules.join(", ")\r
43     end\r
44   end\r
45 \r
46   def handle_get(m, params)\r
47     key = params[:key].to_s.intern\r
48     unless @bot.config.items.has_key?(key)\r
49       m.reply _("no such config key %{key}") % {:key => key}\r
50       return\r
51     end\r
52     return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)\r
53     value = @bot.config.items[key].to_s\r
54     m.reply "#{key}: #{value}"\r
55   end\r
56 \r
57   def handle_desc(m, params)\r
58     key = params[:key].to_s.intern\r
59     unless @bot.config.items.has_key?(key)\r
60       m.reply _("no such config key %{key}") % {:key => key}\r
61     end\r
62     m.reply "#{key}: #{@bot.config.items[key].desc}"\r
63   end\r
64 \r
65   def handle_search(m, params)\r
66     rx = Regexp.new(params[:rx].to_s, true)\r
67     cfs = []\r
68     @bot.config.items.each do |k, v|\r
69       cfs << v if k.to_s.match(rx) or (v.desc.match(rx) rescue false)\r
70     end\r
71     if cfs.empty?\r
72       m.reply _("no config key found matching %{r}") % { :r => params[:rx].to_s}\r
73     else\r
74       m.reply _("possible keys: %{kl}") % { :kl => cfs.map { |c| c.key}.join(', ') }\r
75       m.reply cfs.map { |c| [c.key, c.desc].join(': ') }.join("\n")\r
76     end\r
77   end\r
78 \r
79   def handle_unset(m, params)\r
80     key = params[:key].to_s.intern\r
81     unless @bot.config.items.has_key?(key)\r
82       m.reply _("no such config key %{key}") % {:key => key}\r
83     end\r
84     return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)\r
85     @bot.config.items[key].unset\r
86     handle_get(m, params)\r
87     m.reply _("this config change will take effect on the next restart") if @bot.config.items[key].requires_restart\r
88     m.reply _("this config change will take effect on the next rescan") if @bot.config.items[key].requires_rescan\r
89   end\r
90 \r
91   def handle_set(m, params)\r
92     key = params[:key].to_s.intern\r
93     value = params[:value].join(" ")\r
94     unless @bot.config.items.has_key?(key)\r
95       m.reply _("no such config key %{key}") % {:key => key} unless params[:silent]\r
96       return false\r
97     end\r
98     return false if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)\r
99     begin\r
100       @bot.config.items[key].set_string(value)\r
101     rescue ArgumentError => e\r
102       m.reply _("failed to set %{key}: %{error}") % {:key => key, :error => e.message} unless params[:silent]\r
103       return false\r
104     end\r
105     if @bot.config.items[key].requires_restart\r
106       m.reply _("this config change will take effect on the next restart") unless params[:silent]\r
107       return :restart\r
108     elsif @bot.config.items[key].requires_rescan\r
109       m.reply _("this config change will take effect on the next rescan") unless params[:silent]\r
110       return :rescan\r
111     else\r
112       m.okay unless params[:silent]\r
113       return true\r
114     end\r
115   end\r
116 \r
117   def handle_add(m, params)\r
118     key = params[:key].to_s.intern\r
119     value = params[:value]\r
120     unless @bot.config.items.has_key?(key)\r
121       m.reply _("no such config key %{key}") % {:key => key}\r
122       return\r
123     end\r
124     unless @bot.config.items[key].kind_of?(Config::ArrayValue)\r
125       m.reply _("config key %{key} is not an array") % {:key => key}\r
126       return\r
127     end\r
128     return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)\r
129     begin\r
130       @bot.config.items[key].add(value)\r
131     rescue ArgumentError => e\r
132       m.reply _("failed to add %{value} to %{key}: %{error}") % {:value => value, :key => key, :error => e.message}\r
133       return\r
134     end\r
135     handle_get(m,{:key => key})\r
136     m.reply _("this config change will take effect on the next restart") if @bot.config.items[key].requires_restart\r
137     m.reply _("this config change will take effect on the next rescan") if @bot.config.items[key].requires_rescan\r
138   end\r
139 \r
140   def handle_rm(m, params)\r
141     key = params[:key].to_s.intern\r
142     value = params[:value]\r
143     unless @bot.config.items.has_key?(key)\r
144       m.reply _("no such config key %{key}") % {:key => key}\r
145       return\r
146     end\r
147     unless @bot.config.items[key].kind_of?(Config::ArrayValue)\r
148       m.reply _("config key %{key} is not an array") % {:key => key}\r
149       return\r
150     end\r
151     return if !@bot.auth.allow?(@bot.config.items[key].auth_path, m.source, m.replyto)\r
152     begin\r
153       @bot.config.items[key].rm(value)\r
154     rescue ArgumentError => e\r
155       m.reply _("failed to remove %{value} from %{key}: %{error}") % {:value => value, :key => key, :error => e.message}\r
156       return\r
157     end\r
158     handle_get(m,{:key => key})\r
159     m.reply _("this config change will take effect on the next restart") if @bot.config.items[key].requires_restart\r
160     m.reply _("this config change will take effect on the next rescan") if @bot.config.items[key].requires_rescan\r
161   end\r
162 \r
163   def bot_save(m, param)\r
164     @bot.save\r
165     m.okay\r
166   end\r
167 \r
168   def bot_rescan(m, param)\r
169     m.reply _("saving ...")\r
170     @bot.save\r
171     m.reply _("rescanning ...")\r
172     @bot.rescan\r
173     m.reply _("done. %{plugin_status}") % {:plugin_status => @bot.plugins.status(true)}\r
174   end\r
175 \r
176   def bot_nick(m, param)\r
177     @bot.nickchg(param[:nick])\r
178   end\r
179 \r
180   def bot_status(m, param)\r
181     m.reply @bot.status\r
182   end\r
183 \r
184   # TODO is this one of the methods that disappeared when the bot was moved\r
185   # from the single-file to the multi-file registry?\r
186   #\r
187   #  def bot_reg_stat(m, param)\r
188   #    m.reply @registry.stat.inspect\r
189   #  end\r
190 \r
191   def bot_version(m, param)\r
192     m.reply version_string\r
193   end\r
194 \r
195   def ctcp_listen(m)\r
196     who = m.private? ? "me" : m.target\r
197     case m.ctcp.intern\r
198     when :VERSION\r
199       m.ctcp_reply version_string\r
200       @bot.irclog "@ #{m.source} asked #{who} about version info"\r
201     when :SOURCE\r
202       m.ctcp_reply Irc::Bot::SOURCE_URL\r
203       @bot.irclog "@ #{m.source} asked #{who} about source info"\r
204     end\r
205   end\r
206 \r
207   def handle_help(m, params)\r
208     m.reply help(params[:topic])\r
209   end\r
210 \r
211   def help(plugin, topic="")\r
212     case plugin\r
213     when "config"\r
214       case topic\r
215       when "list"\r
216       _("config list => list configuration modules, config list <module> => list configuration keys for module <module>")\r
217       when "get"\r
218       _("config get <key> => get configuration value for key <key>")\r
219       when "unset"\r
220       _("reset key <key> to the default")\r
221       when "set"\r
222       _("config set <key> <value> => set configuration value for key <key> to <value>")\r
223       when "desc"\r
224       _("config desc <key> => describe what key <key> configures")\r
225       when "add"\r
226       _("config add <value> to <key> => add value <value> to key <key> if <key> is an array")\r
227       when "rm"\r
228       _("config rm <value> from <key> => remove value <value> from key <key> if <key> is an array")\r
229       else\r
230       _("config module - bot configuration. usage: list, desc, get, set, unset, add, rm")\r
231       # else\r
232       #   "no help for config #{topic}"\r
233       end\r
234     when "nick"\r
235       _("nick <newnick> => change the bot nick to <newnick>, if possible")\r
236     when "status"\r
237       _("status => display some information on the bot's status")\r
238     when "save"\r
239       _("save => save current dynamic data and configuration")\r
240     when "rescan"\r
241       _("rescan => reload modules and static facts")\r
242     when "version"\r
243       _("version => describes software version")\r
244     else\r
245       _("config-related tasks: config, save, rescan, version, nick, status")\r
246     end\r
247   end\r
248 \r
249 end\r
250 \r
251 conf = ConfigModule.new\r
252 \r
253 conf.map 'config list :module',\r
254   :action => 'handle_list',\r
255   :defaults => {:module => false},\r
256   :auth_path => 'show'\r
257 # TODO this one is presently a security risk, since the bot\r
258 # stores the master password in the config. Do we need auth levels\r
259 # on the Bot::Config keys too?\r
260 conf.map 'config get :key',\r
261   :action => 'handle_get',\r
262   :auth_path => 'show'\r
263 conf.map 'config desc :key',\r
264   :action => 'handle_desc',\r
265   :auth_path => 'show'\r
266 conf.map 'config describe :key',\r
267   :action => 'handle_desc',\r
268   :auth_path => 'show::desc!'\r
269 conf.map 'config search *rx',\r
270   :action => 'handle_search',\r
271   :auth_path => 'show'\r
272 \r
273 conf.map "save",\r
274   :action => 'bot_save'\r
275 conf.map "rescan",\r
276   :action => 'bot_rescan'\r
277 conf.map "nick :nick",\r
278   :action => 'bot_nick'\r
279 conf.map "status",\r
280   :action => 'bot_status',\r
281   :auth_path => 'show::status'\r
282 # TODO see above\r
283 #\r
284 # conf.map "registry stats",\r
285 #   :action => 'bot_reg_stat',\r
286 #   :auth_path => '!config::status'\r
287 conf.map "version",\r
288   :action => 'bot_version',\r
289   :auth_path => 'show::status'\r
290 \r
291 conf.map 'config set :key *value',\r
292   :action => 'handle_set',\r
293   :auth_path => 'edit'\r
294 conf.map 'config add :value to :key',\r
295   :action => 'handle_add',\r
296   :auth_path => 'edit'\r
297 conf.map 'config rm :value from :key',\r
298   :action => 'handle_rm',\r
299   :auth_path => 'edit'\r
300 conf.map 'config del :value from :key',\r
301   :action => 'handle_rm',\r
302   :auth_path => 'edit'\r
303 conf.map 'config delete :value from :key',\r
304   :action => 'handle_rm',\r
305   :auth_path => 'edit'\r
306 conf.map 'config unset :key',\r
307   :action => 'handle_unset',\r
308   :auth_path => 'edit'\r
309 conf.map 'config reset :key',\r
310   :action => 'handle_unset',\r
311   :auth_path => 'edit'\r
312 \r
313 conf.map 'config help :topic',\r
314   :action => 'handle_help',\r
315   :defaults => {:topic => false},\r
316   :auth_path => '!help!'\r
317 \r
318 conf.default_auth('*', false)\r
319 conf.default_auth('show', true)\r
320 conf.default_auth('show::get', false)\r
321 # TODO these shouldn't be set here, we need a way to let the default\r
322 # permission be specified together with the ConfigValue\r
323 conf.default_auth('key', true)\r
324 conf.default_auth('key::auth::password', false)\r
325 \r