require 'openssl'
require 'cgi'
require 'json'
+require 'erb'
+require 'ostruct'
module ::Irc
class Bot
# A WebMessage is a web request and response object combined with helper methods.
#
class WebMessage
- attr_reader :bot, :method, :bot, :req, :res, :post, :client, :path, :source
+ # Bot instance
+ #
+ attr_reader :bot
+ # HTTP method (POST, GET, etc.)
+ #
+ attr_reader :method
+ # Request object, a instance of WEBrick::HTTPRequest ({http://www.ruby-doc.org/stdlib-2.0/libdoc/webrick/rdoc/WEBrick/HTTPRequest.html docs})
+ #
+ attr_reader :req
+ # Response object, a instance of WEBrick::HTTPResponse ({http://www.ruby-doc.org/stdlib-2.0/libdoc/webrick/rdoc/WEBrick/HTTPResponse.html docs})
+ #
+ attr_reader :res
+ # Parsed post request parameters.
+ #
+ attr_reader :post
+ # Parsed url parameters.
+ #
+ attr_reader :args
+ # Client IP.
+ #
+ attr_reader :client
+ # URL Path.
+ #
+ attr_reader :path
+ # The bot user issuing the command.
+ #
+ attr_reader :source
def initialize(bot, req, res)
@bot = bot
@req = req
@res = res
@method = req.request_method
+ @post = {}
if req.body and not req.body.empty?
- @post = CGI::parse(req.body)
+ @post = parse_query(req.body)
+ end
+ @args = {}
+ if req.query_string and not req.query_string.empty?
+ @args = parse_query(req.query_string)
end
@client = req.peeraddr[3]
if botuser and botuser.password == password
@source = botuser
true
+ else
+ false
end
- false
else
true # no need to request auth at this point
end
}
@path = req.path
- debug '@path = ' + @path.inspect
+
+ @load_path = [File.join(Config::datadir, 'web')]
+ @load_path += @bot.plugins.core_module_dirs
+ @load_path += @bot.plugins.plugin_dirs
+ end
+
+ def parse_query(query)
+ params = CGI::parse(query)
+ params.each_pair do |key, val|
+ params[key] = val.last
+ end
+ params
end
# The target of a RemoteMessage
true
end
- # Sends a plaintext response
- def send_plaintext(body, status=200)
+ # Sends a response with the specified body, status and content type.
+ def send_response(body, status, type)
@res.status = status
- @res['Content-Type'] = 'text/plain'
+ @res['Content-Type'] = type
@res.body = body
end
+
+ # Sends a plaintext response
+ def send_plaintext(body, status=200)
+ send_response(body, status, 'text/plain')
+ end
+
+ # Sends a json response
+ def send_json(body, status=200)
+ send_response(body, status, 'application/json')
+ end
+
+ # Sends a html response
+ def send_html(body, status=200)
+ send_response(body, status, 'text/html')
+ end
+
+ # Expands a relative filename to absolute using a list of load paths.
+ def get_load_path(filename)
+ @load_path.each do |path|
+ file = File.join(path, filename)
+ return file if File.exists?(file)
+ end
+ end
+
+ # Renders a erb template and responds it
+ def render(template, args={})
+ file = get_load_path template
+ if not file
+ raise 'template not found: ' + template
+ end
+
+ tmpl = ERB.new(IO.read(file))
+ ns = OpenStruct.new(args)
+ body = tmpl.result(ns.instance_eval { binding })
+ send_html(body, 200)
+ end
end
# works similar to a message mapper but for url paths
tmpl = @templates[index]
raise "Botmodule #{botmodule.name} tried to unmap #{tmpl.inspect} that was handled by #{tmpl.botmodule}" unless tmpl.botmodule == botmodule.name
debug "Unmapping #{tmpl.inspect}"
- @templates[handle] = nil
+ @templates[index] = nil
@templates.clear unless @templates.compact.size > 0
end
if m.bot.auth.permit?(m.source || Auth::defaultbotuser, auth, '?')
debug "template match found and auth'd: #{action.inspect} #{params.inspect}"
response = botmodule.send(action, m, params)
- if m.res.sent_size == 0
- m.send_plaintext(response.to_json)
+ if m.res.sent_size == 0 and m.res.body.empty?
+ m.send_json(response.to_json)
end
return true
end
web_cleanup
end
end
+
+ # And just because I like consistency:
+ #
+ module WebCoreBotModule
+ include WebBotModule
+ end
+
+ module WebPlugin
+ include WebBotModule
+ end
end
end # Bot
end # Irc
begin
m = WebMessage.new(@bot, req, res)
@bot.web_dispatcher.handle m
+ rescue WEBrick::HTTPStatus::Unauthorized
+ res.status = 401
+ res['Content-Type'] = 'text/plain'
+ res.body = 'Authentication Required!'
+ error 'authentication error (wrong password)'
rescue
res.status = 500
res['Content-Type'] = 'text/plain'
class WebServiceModule < CoreBotModule
- include WebBotModule
+ include WebCoreBotModule
Config.register Config::BooleanValue.new('webservice.autostart',
:default => false,
:requires_rescan => true,
:desc => 'Host the web service will bind on')
+ Config.register Config::StringValue.new('webservice.url',
+ :default => 'http://127.0.0.1:7268',
+ :desc => 'The public URL of the web service.')
+
Config.register Config::BooleanValue.new('webservice.ssl',
:default => false,
:requires_rescan => true,
return
end
- command = m.post['command'][0]
+ command = m.post['command']
if not m.source
botuser = Auth::defaultbotuser
else
- botuser = m.source.botuser
+ botuser = m.source
end
netmask = '%s!%s@%s' % [botuser.username, botuser.username, m.client]
+ debug 'dispatch command: ' + command
+
user = WebServiceUser.new(netmask, botuser)
message = Irc::PrivMessage.new(@bot, nil, user, @bot.myself, command)
res = @bot.plugins.irc_delegate('privmsg', message)
- { :reply => user.response }
+ if m.req['Accept'] == 'application/json'
+ { :reply => user.response }
+ else
+ m.send_plaintext(user.response.join("\n") + "\n")
+ end
end
end