# frozen_string_literal: true
module ESM
class Websocket
class ServerRequest
ALLOWLISTED_SERVER_COMMANDS = %w[
server_initialization
xm8_notification
discord_log
discord_message_channel
].freeze
def initialize(connection:, message:)
@connection = connection
@message = message
@request = @connection.requests[message.commandID]
@request.on_reply(connection) if @request.present?
end
# These are normally empty responses
# message.returned is legacy
def invalid?
@message.ignore || @message.returned
end
# Checks if the request should be removed on the first ignore
def remove_on_ignore?
@request ? @request.remove_on_ignore : false
end
def process
if @request.present?
process_command_response
else
process_server_command
end
end
# Removes the request from the queue if present
def remove_request
@connection.remove_request(@message.commandID) if @request
end
private
# @private
# Processes a command response from the A3 server.
def process_command_response
# Save this response on the request
@request.response = @message.parameters
# Logging
command = @request.command
info!(command: command.to_h, response: @message) if command
# We have an error from the DLL
check_for_command_error!
# Execute the command
begin
command.from_server(@message.parameters)
rescue ESM::Exception::CheckFailure => e
# This catches any errors from the command.
command.reply(e.data)
end
# This catches the check_for_command_error
rescue ESM::Exception::CheckFailure => e
on_command_error(e.data)
ensure
# Make sure to remove the request no matter what
remove_request
end
# @private
# Processes server command that doesn't come from a request.
def process_server_command
return if !ALLOWLISTED_SERVER_COMMANDS.include?(@message.command)
# Discord events are in ESM::Discord::Event, game-server events are in ESM::Event
namespace = @message.command.start_with?("discord_") ? "ESM::Discord::Event" : "ESM::Event"
# Build the class and call it
"#{namespace}::#{@message.command.classify}V1".constantize.new(
connection: @connection,
server: @connection.server,
parameters: @message.parameters.first
).run!
end
# @private
# Reports the error back to the user so they know the command failed
def on_command_error(error)
return if @request.user.nil?
# Reset the current cooldown
@request.command.current_cooldown&.reset!
# Some errors from the dll already have a mention in them...
error = "#{@request.user.mention}, #{error}" if !error.start_with?("<")
# Some errors have commands hardcoded...
territories_command = ESM::Command.get(:territories)
error = error.gsub(
/!territories (#{Regex::SERVER_ID.source})/,
territories_command.usage(arguments: {server_id: "\\1"}) # \1 will be replaced with the server_id by gsub
)
# Send the error message
embed = ESM::Embed.build(:error, description: error)
@request.command.reply(embed)
end
# Error responses from a command
#
# @private
def check_for_command_error!
raise ESM::Exception::CheckFailure, @message.error if @message.error.present?
raise ESM::Exception::CheckFailure, @message.parameters.first.error if @message.parameters&.first&.error&.present?
end
end
end
end