]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/alias.rb
added 'alias rm' as alternative for 'alias remove'
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / alias.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Alias plugin for rbot
5 #
6 # Author:: Yaohan Chen <yaohan.chen@gmail.com>
7 # Copyright:: (C) 2007 Yaohan Chen
8 # License:: GPLv2
9 #
10 # This plugin allows defining aliases for rbot commands. Aliases are like normal rbot
11 # commands and can take parameters. When called, they will be substituted into an
12 # exisitng rbot command and that is run.
13 #
14 # == Example Session
15 #   < alias googlerbot *terms => google site:linuxbrit.co.uk/rbot/ <terms>
16 #   > okay
17 #   < googlerbot plugins
18 #   > Results for site:linuxbrit.co.uk/rbot/ plugins: ....
19 #
20 # == Security
21 # By default, only the owner can define and remove aliases, while everyone else can
22 # use and view them. When a command is executed with an alias, it's mapped normally with
23 # the alias user appearing to attempt to execute the command. Therefore it should be not
24 # possible to use aliases to circumvent permission sets. Care should be taken when
25 # defining aliases, due to these concerns:
26 # * Defined aliases can potentially override other plugins' maps, if this plugin is
27 #   loaded first
28 # * Aliases can cause infinite recursion of aliases and/or commands. The plugin attempts
29 #   to detect and stop this, but a few recursive calls can still cause spamming
30
31 require 'yaml'
32
33 class AliasPlugin < Plugin
34   # an exception raised when loading or getting input of invalid alias definitions
35   class AliasDefinitionError < ArgumentError
36   end
37
38   MAX_RECURSION_DEPTH = 10
39
40   def initialize
41     super
42     @data_path = "#{@bot.botclass}/alias/"
43     @data_file = "#{@data_path}/aliases.yaml"
44     # hash of alias => command entries
45     @aliases = if File.exist?(@data_file)
46                  YAML.load_file(@data_file)
47                else
48                  Hash.new
49                end
50     @aliases.each_pair do |a, c|
51       begin
52         add_alias(a, c)
53       rescue AliasDefinitionError
54         warning _("Invalid alias entry %{alias} : %{command} in %{filename}: %{reason}") %
55                 {:alias => a, :command => c, :filename => @data_file, :reason => $1}
56       end
57     end 
58   end 
59
60   def save 
61     Dir.mkdir(@data_path) unless File.exist?(@data_path)
62     File.open(@data_file, 'w') {|f| f.write @aliases.to_yaml}
63   end
64
65   def cmd_add(m, params)
66     begin
67       add_alias(params[:text].to_s, params[:command].to_s)
68       m.okay
69     rescue AliasDefinitionError
70       m.reply _('The definition you provided is invalid: %{reason}') % {:reason => $!}
71     end
72   end
73
74   def cmd_remove(m, params)
75     text = params[:text].to_s
76     if @aliases.has_key?(text)
77       @aliases.delete(text)
78       # TODO when rbot supports it, remove the mapping corresponding to the alias
79       m.okay
80     else
81       m.reply _('No such alias is defined')
82     end
83   end
84
85   def cmd_list(m, params)
86     if @aliases.empty?
87       m.reply _('No aliases defined')
88     else
89       m.reply @aliases.map {|a, c| "#{a} => #{c}"}.join(' | ')
90     end
91   end
92
93   def cmd_whatis(m, params)
94     text = params[:text].to_s
95     if @aliases.has_key?(text)
96       m.reply _('Alias of %{command}') % {:command => @aliases[text]}
97     else
98       m.reply _('No such alias is defined')
99     end
100   end
101
102   def add_alias(text, command)
103     # each alias is implemented by adding a message map, whose handler creates a message
104     # containing the aliased command
105
106     command.grep(/<(\w+)>/) {$1}.all? {|s| text =~ /(?:^|\s)[:*]#{s}(?:\s|$)/ } or
107       raise AliasDefinitionError.new(_('Not all substitutions in command text have matching arguments in alias text'))
108     
109     @aliases[text] = command
110     map text, :action => :"alias_handle<#{text}>", :auth_path => 'run'
111   end
112
113   def respond_to?(name, include_private=false)
114     name.to_s =~ /\Aalias_handle<.+>\Z/ || super
115   end
116
117   def method_missing(name, *args, &block)
118     if name.to_s =~ /\Aalias_handle<(.+)>\Z/
119       m, params = args
120       # messages created by alias handler will have a depth method, which returns the 
121       # depth of "recursion" caused by the message
122       current_depth = if m.respond_to?(:depth) then m.depth else 0 end
123       if current_depth > MAX_RECURSION_DEPTH
124         m.reply _('The alias seems to have caused infinite recursion. Please examine your alias definitions')
125         return
126       end
127
128       command = @aliases[$1]
129       if command
130         # create a fake message containing the intended command
131         new_msg = PrivMessage.new(@bot, m.server, m.server.user(m.source), m.target,
132                                     command.gsub(/<(\w+)>/) {|arg| params[:"#{$1}"].to_s})
133         # tag incremented depth on the message
134         class << new_msg
135           self
136         end.send(:define_method, :depth) {current_depth + 1}
137
138         @bot.plugins.privmsg(new_msg)
139       else
140         m.reply _("Error handling the alias, the command is not defined")
141       end
142     else
143       super(name, *args, &block)
144     end
145   end
146
147   def help(plugin, topic='')
148     case topic
149     when ''
150       _('Create and use aliases for commands. Topics: create, commands')
151     when 'create'
152       _('"alias <text> => <command>" => add text as an alias of command. Text can contain placeholders marked with : or * for :words and *multiword arguments. The command can contain placeholders enclosed with < > which will be substituded with argument values. For example: alias googlerbot *terms => google site:linuxbrit.co.uk/rbot/ <terms>')
153     when 'commands'
154       _('alias list => list defined aliases | alias whatis <alias> => show definition of the alias | alias remove <alias> => remove defined alias | see the "create" topic about adding aliases')
155     end
156   end
157 end
158
159 plugin = AliasPlugin.new
160 plugin.default_auth('edit', false)
161 plugin.default_auth('run', true)
162 plugin.default_auth('list', true)
163
164 plugin.map 'alias list',
165            :action => :cmd_list,
166            :auth_path => 'view'
167 plugin.map 'alias whatis *text',
168            :action => :cmd_whatis,
169            :auth_path => 'view'
170 plugin.map 'alias remove *text',
171            :action => :cmd_remove,
172            :auth_path => 'edit'
173 plugin.map 'alias rm *text',
174            :action => :cmd_remove,
175            :auth_path => 'edit'
176 plugin.map 'alias *text => *command',
177            :action => :cmd_add,
178            :auth_path => 'edit'
179
180
181