require "net/http" require "uri" class Webhook < ApplicationRecord ENDPOINT_TIMEOUT = 7.seconds belongs_to :user def deliver(message) post(payload(message)).tap do |response| if text = extract_text_from(response) receive_text_reply_to(message.room, text: text) elsif attachment = extract_attachment_from(response) receive_attachment_reply_to(message.room, attachment: attachment) end end rescue Net::OpenTimeout, Net::ReadTimeout receive_text_reply_to message.room, text: "Failed to respond within #{ENDPOINT_TIMEOUT} seconds" end private def post(payload) http.request \ Net::HTTP::Post.new(uri, "Content-Type" => "application/json").tap { |request| request.body = payload } end def http Net::HTTP.new(uri.host, uri.port).tap do |http| http.use_ssl = (uri.scheme == "https") http.open_timeout = ENDPOINT_TIMEOUT http.read_timeout = ENDPOINT_TIMEOUT end end def uri @uri ||= URI(url) end def payload(message) { user: { id: message.creator.id, name: message.creator.name }, room: { id: message.room.id, name: message.room.name, path: room_bot_messages_path(message) }, message: { id: message.id, body: { html: message.body.body, plain: without_recipient_mentions(message.plain_text_body) }, path: message_path(message) } }.to_json end def message_path(message) Rails.application.routes.url_helpers.room_at_message_path(message.room, message) end def room_bot_messages_path(message) Rails.application.routes.url_helpers.room_bot_messages_path(message.room, user.bot_key) end def extract_text_from(response) String.new(response.body).force_encoding("UTF-8") if response.code == "200" && response.content_type.in?(%w[ text/html text/plain ]) end def receive_text_reply_to(room, text:) room.messages.create!(body: text, creator: user).broadcast_create end def extract_attachment_from(response) if response.content_type && mime_type = Mime::Type.lookup(response.content_type) ActiveStorage::Blob.create_and_upload! \ io: StringIO.new(response.body), filename: "attachment.#{mime_type.symbol}", content_type: mime_type.to_s end end def receive_attachment_reply_to(room, attachment:) room.messages.create_with_attachment!(attachment: attachment, creator: user).broadcast_create end def without_recipient_mentions(body) body \ .gsub(user.attachable_plain_text_representation(nil), "") # Remove mentions of the recipient user .gsub(/\A\p{Space}+|\p{Space}+\z/, "") # Remove leading and trailing whitespace uncluding unicode spaces end end