mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2025-12-21 07:32:02 +00:00
changes: Maps are no longer compiled in, instead loaded directly from the DMMs at runtime. Z level defines have been moved from the config to map datums. Unit tests now use typecaches. DMMS now actually works. DMMS has been updated slightly. DMMS is now capable of loading simple lists of non-text types. DMMS is now faster when loading many types without mapped in attributes and when loading area instances. Asteroid generation is now defined on the map datum instead of being hard-coded in SSasteroid. Holodeck presets are now defined on the map datum. Atmos machinery now uses Initialize().
1008 lines
33 KiB
Plaintext
1008 lines
33 KiB
Plaintext
//
|
|
// This file contains the API commands for the serverside API
|
|
//
|
|
// IMPORTANT:
|
|
// When changing api commands always update the version number of the API
|
|
// The version number is defined in /datum/topic_command/api_get_version
|
|
|
|
//Init the API at startup
|
|
/hook/startup/proc/setup_api()
|
|
for (var/path in typesof(/datum/topic_command) - /datum/topic_command)
|
|
var/datum/topic_command/A = new path()
|
|
if(A != null)
|
|
topic_commands[A.name] = A
|
|
topic_commands_names.Add(A.name)
|
|
listclearnulls(topic_commands)
|
|
listclearnulls(topic_commands_names)
|
|
|
|
if (config.api_rate_limit_whitelist.len)
|
|
// To make the api_rate_limit_whitelist[addr] grabs actually work.
|
|
for (var/addr in config.api_rate_limit_whitelist)
|
|
config.api_rate_limit_whitelist[addr] = 1
|
|
|
|
return 1
|
|
|
|
/world/proc/api_do_auth_check(var/addr, var/auth, var/datum/topic_command/command)
|
|
//Check if command is on nothrottle list
|
|
if(command.no_throttle == 1)
|
|
log_debug("API: Throttling bypassed - Command [command.name] set to no_throttle")
|
|
else
|
|
if(world_api_rate_limit[addr] != null && config.api_rate_limit_whitelist[addr] == null) //Check if the ip is in the rate limiting list and not in the whitelist
|
|
if(abs(world_api_rate_limit[addr] - world.time) < config.api_rate_limit) //Check the last request time of the ip
|
|
world_api_rate_limit[addr] = world.time // Set the time of the last request
|
|
return 2 //Throttled
|
|
world_api_rate_limit[addr] = world.time // Set the time of the last request
|
|
|
|
|
|
//Check if the command is on the auth whitelist
|
|
if(command.no_auth == 1)
|
|
log_debug("API: Auth bypassed - Command [command.name] set to no_auth")
|
|
return 0 // Authed (bypassed)
|
|
|
|
var/DBQuery/authquery = dbcon.NewQuery({"SELECT api_f.command
|
|
FROM ss13_api_token_command as api_t_f, ss13_api_tokens as api_t, ss13_api_commands as api_f
|
|
WHERE api_t.id = api_t_f.token_id AND api_f.id = api_t_f.command_id
|
|
AND api_t.deleted_at IS NULL
|
|
AND (
|
|
(token = :token: AND ip = :ip: AND command = :command:)
|
|
OR
|
|
(token = :token: AND ip IS NULL AND command = :command:)
|
|
OR
|
|
(token = :token: AND ip = :ip: AND command = \"_ANY\")
|
|
OR
|
|
(token = :token: AND ip IS NULL AND command = \"_ANY\")
|
|
OR
|
|
(token IS NULL AND ip IS NULL AND command = :command:)
|
|
)"})
|
|
//Check if the token is not deleted
|
|
//Check if one of the following is true:
|
|
// Full Match - Token IP and Command Matches
|
|
// Any IP - Token and Command Matches, IP is set to NULL (not required)
|
|
// Any Command - Token and IP Matches, Command is set to _ANY
|
|
// Any Command, Any IP - Token Matches, IP is set to NULL (not required), Command is set to _ANY
|
|
// Public - Token is set to NULL, IP is set to NULL and command matches
|
|
|
|
authquery.Execute(list("token" = auth, "ip" = addr, "command" = command.name))
|
|
log_debug("API: Auth Check - Query Executed - Returned Rows: [authquery.RowCount()]")
|
|
|
|
if (authquery.RowCount())
|
|
return 0 // Authed
|
|
return 1 // Bad Key
|
|
|
|
|
|
proc/api_update_command_database()
|
|
log_debug("API: DB Command Update Called")
|
|
//Check if DB Connection is established
|
|
if (!establish_db_connection(dbcon))
|
|
return 0 //Error
|
|
|
|
var/DBQuery/commandinsertquery = dbcon.NewQuery({"INSERT INTO ss13_api_commands (command,description)
|
|
VALUES (:command_name:,:command_description:)
|
|
ON DUPLICATE KEY UPDATE description = :command_description:;"})
|
|
|
|
for(var/com in topic_commands)
|
|
var/datum/topic_command/command = topic_commands[com]
|
|
commandinsertquery.Execute(list("command_name" = command.name, "command_description" = command.description))
|
|
log_debug("API: DB Command Update Executed")
|
|
return 1 //OK
|
|
|
|
//API Boilerplate
|
|
/datum/topic_command
|
|
var/name = null //Name for the command
|
|
var/no_auth = 0 //If the user does NOT need to be authed to use the command
|
|
var/no_throttle = 0 //If this command should NOT be limited by the throtteling
|
|
var/description = null //Description for the command
|
|
var/list/params = list() //Required Parameters for the command
|
|
//Explanation of the parameter options:
|
|
//Required - name -> Name of the parameter - should be the same as the index in the list
|
|
//Required - desc -> Description of the parameter
|
|
//Required - req -> Is this a required parameter: 1 -> Yes, 0 -> No
|
|
//Required - type -> What type is this:
|
|
// str->String,
|
|
// int->Integer,
|
|
// lst->List/array,
|
|
// senderkey->unique identifier of the person sending the request
|
|
// slct -> Select one of multiple specified options
|
|
//Required* - options -> The possible options that can be selected (slct)
|
|
var/statuscode = null
|
|
var/response = null
|
|
var/data = null
|
|
/datum/topic_command/proc/run_command(queryparams)
|
|
// Always returns 1 --> Details status in statuscode, response and data
|
|
return 1
|
|
/datum/topic_command/proc/check_params_missing(queryparams)
|
|
//Check if some of the required params are missing
|
|
// 0 -> if all params are supplied
|
|
// >=1 -> if a param is missing
|
|
var/list/missing_params = list()
|
|
var/errorcount = 0
|
|
|
|
for(var/key in params)
|
|
var/list/param = params[key]
|
|
if(queryparams[key] == null)
|
|
if(param["req"] == 0)
|
|
log_debug("API: The following parameter is OPTIONAL and missing: [param["name"]] - [param["desc"]]")
|
|
else
|
|
log_debug("API: The following parameter is REQUIRED but missing: [param["name"]] - [param["desc"]]")
|
|
errorcount ++
|
|
missing_params += param["name"]
|
|
if(errorcount)
|
|
log_debug("API: Request aborted. Required parameters missing")
|
|
statuscode = 400
|
|
response = "Required params missing"
|
|
data = missing_params
|
|
return errorcount
|
|
return 0
|
|
|
|
//
|
|
// API for the API
|
|
//
|
|
/datum/topic_command/api_get_version
|
|
name = "api_get_version"
|
|
description = "Gets the version of the API"
|
|
no_auth = 1
|
|
no_throttle = 1
|
|
/datum/topic_command/api_get_version/run_command(queryparams)
|
|
var/list/version = list()
|
|
var/versionstring = null
|
|
//The Version Number follows SemVer http://semver.org/
|
|
version["major"] = 2 //Major Version Number --> Increment when implementing breaking changes
|
|
version["minor"] = 0 //Minor Version Number --> Increment when adding features
|
|
version["patch"] = 0 //Patchlevel --> Increment when fixing bugs
|
|
|
|
versionstring = "[version["major"]].[version["minor"]].[version["patch"]]"
|
|
|
|
statuscode = 200
|
|
response = versionstring
|
|
data = version
|
|
return 1
|
|
|
|
|
|
//Get all the commands a specific token / ip combo is authorized to use
|
|
/datum/topic_command/api_get_authed_commands
|
|
name = "api_get_authed_commands"
|
|
description = "Returns the commands that can be accessed by the requesting ip and token"
|
|
/datum/topic_command/api_get_authed_commands/run_command(queryparams)
|
|
var/list/commands = list()
|
|
|
|
|
|
//Check if DB Connection is established
|
|
if (!establish_db_connection(dbcon))
|
|
statuscode = 500
|
|
response = "DB Connection Unavailable"
|
|
return 1
|
|
|
|
var/DBQuery/commandsquery = dbcon.NewQuery({"SELECT api_f.command
|
|
FROM ss13_api_token_command as api_t_f, ss13_api_tokens as api_t, ss13_api_commands as api_f
|
|
WHERE api_t.id = api_t_f.token_id AND api_f.id = api_t_f.command_id
|
|
AND (
|
|
(token = :token: AND ip = :ip:)
|
|
OR
|
|
(token = :token: AND ip IS NULL)
|
|
OR
|
|
(token IS NULL AND ip = :ip:)
|
|
)
|
|
ORDER BY command DESC"})
|
|
|
|
|
|
commandsquery.Execute(list("token" = queryparams["auth"], "ip" = queryparams["addr"]))
|
|
while (commandsquery.NextRow())
|
|
commands[commandsquery.item[1]] = commandsquery.item[1]
|
|
if(commandsquery.item[1] == "_ANY")
|
|
statuscode = 200
|
|
response = "Authorized commands retrieved - ALL"
|
|
data = topic_commands_names
|
|
return 1
|
|
|
|
|
|
statuscode = 200
|
|
response = "Authorized commands retrieved"
|
|
data = commands
|
|
return 1
|
|
|
|
//Get details for a specific api command
|
|
/datum/topic_command/api_explain_command
|
|
name = "api_explain_command"
|
|
description = "Explains a specific API command"
|
|
no_throttle = 1
|
|
params = list(
|
|
"command" = list("name"="command","desc"="The name of the API command that should be explained","req"=1,"type"="str")
|
|
)
|
|
/datum/topic_command/api_explain_command/run_command(queryparams)
|
|
var/datum/topic_command/apicommand = topic_commands[queryparams["command"]]
|
|
var/list/commanddata = list()
|
|
|
|
if (isnull(apicommand))
|
|
statuscode = 501
|
|
response = "Not Implemented - The requested command does not exist"
|
|
return 1
|
|
|
|
//Then query for auth
|
|
if (!establish_db_connection(dbcon))
|
|
statuscode = 500
|
|
response = "DB Connection Unavailable"
|
|
return 1
|
|
|
|
var/DBQuery/permquery = dbcon.NewQuery({"SELECT api_f.command
|
|
FROM ss13_api_token_command as api_t_f, ss13_api_tokens as api_t, ss13_api_commands as api_f
|
|
WHERE api_t.id = api_t_f.token_id AND api_f.id = api_t_f.command_id
|
|
AND api_t.deleted_at IS NULL
|
|
AND (
|
|
(token = :token: AND ip = :ip: AND command = :command:)
|
|
OR
|
|
(token = :token: AND ip IS NULL AND command = :command:)
|
|
OR
|
|
(token = :token: AND ip = :ip: AND command = \"_ANY\")
|
|
OR
|
|
(token = :token: AND ip IS NULL AND command = \"_ANY\")
|
|
OR
|
|
(token IS NULL AND ip IS NULL AND command = :command:)
|
|
)"})
|
|
//Get the tokens and the associated commands
|
|
//Check if the token, the ip and the command matches OR
|
|
// the token + command matches and the ip is NULL (commands that can be used by any ip, but require a token)
|
|
// the token + ip matches and the command is NULL (Allow a specific ip with a specific token to use all commands)
|
|
// the token + ip is NULL and the command matches (Allow a specific command to be used without auth)
|
|
|
|
permquery.Execute(list("token" = queryparams["auth"], "ip" = queryparams["addr"], "command" = queryparams["command"]))
|
|
|
|
if (!permquery.RowCount())
|
|
statuscode = 401
|
|
response = "Unauthorized - To access this command"
|
|
return 1
|
|
|
|
commanddata["name"] = apicommand.name
|
|
commanddata["description"] = apicommand.description
|
|
commanddata["params"] = apicommand.params
|
|
|
|
statuscode = 200
|
|
response = "Command data retrieved"
|
|
data = commanddata
|
|
return 1
|
|
|
|
|
|
/datum/topic_command/update_command_database
|
|
name = "update_command_database"
|
|
description = "Updates the available topic commands in the database"
|
|
/datum/topic_command/update_command_database/run_command(queryparams)
|
|
api_update_command_database()
|
|
|
|
statuscode = 200
|
|
response = "Database Updated"
|
|
return 1
|
|
|
|
//
|
|
// API for the other stuff
|
|
//
|
|
|
|
//Char Names
|
|
/datum/topic_command/get_char_list
|
|
name = "get_char_list"
|
|
description = "Provides a list of all characters ingame"
|
|
/datum/topic_command/get_char_list/run_command(queryparams)
|
|
var/list/chars = list()
|
|
|
|
var/list/mobs = sortmobs()
|
|
for(var/mob/M in mobs)
|
|
if(!M.ckey) continue
|
|
chars[M.name] += M.key ? (M.client ? M.key : "[M.key] (DC)") : "No key"
|
|
|
|
statuscode = 200
|
|
response = "Char list fetched"
|
|
data = chars
|
|
return 1
|
|
|
|
//Admin Count
|
|
/datum/topic_command/get_count_admin
|
|
name = "get_count_admin"
|
|
description = "Gets the number of admins connected"
|
|
/datum/topic_command/get_count_admin/run_command(queryparams)
|
|
var/n = 0
|
|
for (var/client/client in clients)
|
|
if (client.holder && client.holder.rights & (R_ADMIN))
|
|
n++
|
|
|
|
statuscode = 200
|
|
response = "Admin count fetched"
|
|
data = n
|
|
return 1
|
|
|
|
//CCIA Count
|
|
/datum/topic_command/get_count_cciaa
|
|
name = "get_count_cciaa"
|
|
description = "Gets the number of ccia connected"
|
|
/datum/topic_command/get_count_ccia/run_command(queryparams)
|
|
var/n = 0
|
|
for (var/client/client in clients)
|
|
if (client.holder && (client.holder.rights & R_CCIAA) && !(client.holder.rights & R_ADMIN))
|
|
n++
|
|
|
|
statuscode = 200
|
|
response = "CCIA count fetched"
|
|
data = n
|
|
return 1
|
|
|
|
//Mod Count
|
|
/datum/topic_command/get_count_mod
|
|
name = "get_count_mod"
|
|
description = "Gets the number of mods connected"
|
|
/datum/topic_command/get_count_mod/run_command(queryparams)
|
|
var/n = 0
|
|
for (var/client/client in clients)
|
|
if (client.holder && (client.holder.rights & R_MOD) && !(client.holder.rights & R_ADMIN))
|
|
n++
|
|
|
|
statuscode = 200
|
|
response = "Mod count fetched"
|
|
data = n
|
|
return 1
|
|
|
|
//Player Count
|
|
/datum/topic_command/get_count_player
|
|
name = "get_count_player"
|
|
description = "Gets the number of players connected"
|
|
/datum/topic_command/get_count_player/run_command(queryparams)
|
|
var/n = 0
|
|
for(var/mob/M in player_list)
|
|
if(M.client)
|
|
n++
|
|
|
|
statuscode = 200
|
|
response = "Player count fetched"
|
|
data = n
|
|
return 1
|
|
|
|
//Get available Fax Machines
|
|
/datum/topic_command/get_faxmachines
|
|
name = "get_faxmachines"
|
|
description = "Gets all available fax machines"
|
|
/datum/topic_command/get_faxmachines/run_command(queryparams)
|
|
var/list/faxlocations = list()
|
|
|
|
for (var/obj/machinery/photocopier/faxmachine/F in allfaxes)
|
|
faxlocations.Add(F.department)
|
|
|
|
statuscode = 200
|
|
response = "Fax machines fetched"
|
|
data = faxlocations
|
|
return 1
|
|
|
|
//Get Fax List
|
|
/datum/topic_command/get_faxlist
|
|
name = "get_faxlist"
|
|
description = "Gets the list of faxes sent / received"
|
|
params = list(
|
|
"faxtype" = list("name"="faxtype","desc"="Type of the faxes that should be retrieved","req"=1,"type"="slct","options"=list("sent","received"))
|
|
)
|
|
/datum/topic_command/get_faxlist/run_command(queryparams)
|
|
var/list/faxes = list()
|
|
switch (queryparams["faxtype"])
|
|
if ("received")
|
|
faxes = arrived_faxes
|
|
if ("sent")
|
|
faxes = sent_faxes
|
|
|
|
if (!faxes || !faxes.len)
|
|
statuscode = 404
|
|
response = "No faxes found"
|
|
data = null
|
|
return 1
|
|
|
|
var/list/output = list()
|
|
for (var/i = 1, i <= faxes.len, i++)
|
|
var/obj/item/a = faxes[i]
|
|
output += "[i]"
|
|
output[i] = a.name ? a.name : "Untitled Fax"
|
|
|
|
statuscode = 200
|
|
response = "Fetched Fax List"
|
|
data = output
|
|
return 1
|
|
|
|
//Get Specific Fax
|
|
/datum/topic_command/get_fax
|
|
name = "get_fax"
|
|
description = "Gets a specific fax that has been sent or received"
|
|
params = list(
|
|
"faxtype" = list("name"="faxtype","desc"="Type of the faxes that should be retrieved","req"=1,"type"="slct","options"=list("sent","received")),
|
|
"faxid" = list("name"="faxid","desc"="ID of the fax that should be retrieved","req"=1,"type"="int")
|
|
)
|
|
/datum/topic_command/get_fax/run_command(queryparams)
|
|
var/list/faxes = list()
|
|
switch (queryparams["faxtype"])
|
|
if ("received")
|
|
faxes = arrived_faxes
|
|
if ("sent")
|
|
faxes = sent_faxes
|
|
|
|
if (!faxes || !faxes.len)
|
|
statuscode = 500
|
|
response = "No faxes found!"
|
|
data = null
|
|
return 1
|
|
|
|
var/fax_id = text2num(queryparams["faxid"])
|
|
if (fax_id > faxes.len || fax_id < 1)
|
|
statuscode = 404
|
|
response = "Invalid Fax ID"
|
|
data = null
|
|
return 1
|
|
|
|
var/output = list()
|
|
if (istype(faxes[fax_id], /obj/item/weapon/paper))
|
|
var/obj/item/weapon/paper/a = faxes[fax_id]
|
|
output["title"] = a.name ? a.name : "Untitled Fax"
|
|
|
|
var/content = replacetext(a.info, "<br>", "\n")
|
|
content = strip_html_properly(content, 0)
|
|
output["content"] = content
|
|
|
|
statuscode = 200
|
|
response = "Fax (Paper) with id [fax_id] retrieved"
|
|
data = output
|
|
return 1
|
|
else if (istype(faxes[fax_id], /obj/item/weapon/photo))
|
|
statuscode = 501
|
|
response = "Fax is a Photo - Unable to send"
|
|
data = null
|
|
return 1
|
|
else if (istype(faxes[fax_id], /obj/item/weapon/paper_bundle))
|
|
var/obj/item/weapon/paper_bundle/b = faxes[fax_id]
|
|
output["title"] = b.name ? b.name : "Untitled Paper Bundle"
|
|
|
|
if (!b.pages || !b.pages.len)
|
|
statuscode = 500
|
|
response = "Fax Paper Bundle is empty - This should not happen"
|
|
data = null
|
|
return 1
|
|
|
|
var/i = 0
|
|
for (var/obj/item/weapon/paper/c in b.pages)
|
|
i++
|
|
var/content = replacetext(c.info, "<br>", "\n")
|
|
content = strip_html_properly(content, 0)
|
|
output["content"] += "Page [i]:\n[content]\n\n"
|
|
|
|
statuscode = 200
|
|
response = "Fax (PaperBundle) retrieved"
|
|
data = output
|
|
return 1
|
|
|
|
statuscode = 500
|
|
response = "Unable to recognize the fax type. Cannot send contents!"
|
|
data = null
|
|
return 1
|
|
|
|
//Get Ghosts
|
|
/datum/topic_command/get_ghosts
|
|
name = "get_ghosts"
|
|
description = "Gets the ghosts"
|
|
/datum/topic_command/get_ghosts/run_command(queryparams)
|
|
var/list/ghosts[] = list()
|
|
ghosts = get_ghosts(1,1)
|
|
|
|
statuscode = 200
|
|
response = "Fetched Ghost list"
|
|
data = ghosts
|
|
return 1
|
|
|
|
// Crew Manifest
|
|
/datum/topic_command/get_manifest
|
|
name = "get_manifest"
|
|
description = "Gets the crew manifest"
|
|
/datum/topic_command/get_manifest/run_command(queryparams)
|
|
var/list/positions = list()
|
|
var/list/set_names = list(
|
|
"heads" = command_positions,
|
|
"sec" = security_positions,
|
|
"eng" = engineering_positions,
|
|
"med" = medical_positions,
|
|
"sci" = science_positions,
|
|
"civ" = civilian_positions,
|
|
"bot" = nonhuman_positions
|
|
)
|
|
|
|
for(var/datum/data/record/t in data_core.general)
|
|
var/name = t.fields["name"]
|
|
var/rank = t.fields["rank"]
|
|
var/real_rank = make_list_rank(t.fields["real_rank"])
|
|
|
|
var/department = 0
|
|
for(var/k in set_names)
|
|
if(real_rank in set_names[k])
|
|
if(!positions[k])
|
|
positions[k] = list()
|
|
positions[k][name] = rank
|
|
department = 1
|
|
if(!department)
|
|
if(!positions["misc"])
|
|
positions["misc"] = list()
|
|
positions["misc"][name] = rank
|
|
|
|
// for(var/k in positions)
|
|
// positions[k] = list2params(positions[k]) // converts positions["heads"] = list("Bob"="Captain", "Bill"="CMO") into positions["heads"] = "Bob=Captain&Bill=CMO"
|
|
|
|
statuscode = 200
|
|
response = "Manifest fetched"
|
|
data = positions
|
|
return 1
|
|
|
|
//Player Ckeys
|
|
/datum/topic_command/get_player_list
|
|
name = "get_player_list"
|
|
description = "Gets a list of connected players"
|
|
params = list(
|
|
"showadmins" = list("name"="show admins","desc"="A boolean to toggle whether or not hidden admins should be shown with proper or improper ckeys.","req"=0,"type"="int")
|
|
)
|
|
/datum/topic_command/get_player_list/run_command(queryparams)
|
|
var/show_hidden_admins = 0
|
|
|
|
if (!isnull(queryparams["showadmins"]))
|
|
show_hidden_admins = text2num(queryparams["showadmins"])
|
|
|
|
var/list/players = list()
|
|
for (var/client/C in clients)
|
|
if (show_hidden_admins && C.holder && C.holder.fakekey)
|
|
players += ckey(C.holder.fakekey)
|
|
else
|
|
players += C.ckey
|
|
|
|
statuscode = 200
|
|
response = "Player list fetched"
|
|
data = players
|
|
return 1
|
|
|
|
//Get info about a specific player
|
|
/datum/topic_command/get_player_info
|
|
name = "get_player_info"
|
|
description = "Gets information about a specific player"
|
|
params = list(
|
|
"search" = list("name"="search","desc"="List with strings that should be searched for","req"=1,"type"="lst")
|
|
)
|
|
/datum/topic_command/get_player_info/run_command(queryparams)
|
|
var/list/search = queryparams["search"]
|
|
|
|
var/list/ckeysearch = list()
|
|
for(var/text in search)
|
|
ckeysearch += ckey(text)
|
|
|
|
var/list/match = list()
|
|
|
|
for(var/mob/M in mob_list)
|
|
var/strings = list(M.name, M.ckey)
|
|
if(M.mind)
|
|
strings += M.mind.assigned_role
|
|
strings += M.mind.special_role
|
|
for(var/text in strings)
|
|
if(ckey(text) in ckeysearch)
|
|
match[M] += 10 // an exact match is far better than a partial one
|
|
else
|
|
for(var/searchstr in search)
|
|
if(findtext(text, searchstr))
|
|
match[M] += 1
|
|
|
|
var/maxstrength = 0
|
|
for(var/mob/M in match)
|
|
maxstrength = max(match[M], maxstrength)
|
|
for(var/mob/M in match)
|
|
if(match[M] < maxstrength)
|
|
match -= M
|
|
|
|
if(!match.len)
|
|
statuscode = 449
|
|
response = "No match found"
|
|
data = null
|
|
return 1
|
|
else if(match.len == 1)
|
|
var/mob/M = match[1]
|
|
var/info = list()
|
|
info["key"] = M.key
|
|
if (M.client)
|
|
var/client/C = M.client
|
|
info["discordmuted"] = C.mute_discord ? "Yes" : "No"
|
|
info["name"] = M.name == M.real_name ? M.name : "[M.name] ([M.real_name])"
|
|
info["role"] = M.mind ? (M.mind.assigned_role ? M.mind.assigned_role : "No role") : "No mind"
|
|
var/turf/MT = get_turf(M)
|
|
info["loc"] = M.loc ? "[M.loc]" : "null"
|
|
info["turf"] = MT ? "[MT] @ [MT.x], [MT.y], [MT.z]" : "null"
|
|
info["area"] = MT ? "[MT.loc]" : "null"
|
|
info["antag"] = M.mind ? (M.mind.special_role ? M.mind.special_role : "Not antag") : "No mind"
|
|
info["hasbeenrev"] = M.mind ? M.mind.has_been_rev : "No mind"
|
|
info["stat"] = M.stat
|
|
info["type"] = M.type
|
|
if(isliving(M))
|
|
var/mob/living/L = M
|
|
info["damage"] = list2params(list(
|
|
oxy = L.getOxyLoss(),
|
|
tox = L.getToxLoss(),
|
|
fire = L.getFireLoss(),
|
|
brute = L.getBruteLoss(),
|
|
clone = L.getCloneLoss(),
|
|
brain = L.getBrainLoss()
|
|
))
|
|
else
|
|
info["damage"] = "non-living"
|
|
info["gender"] = M.gender
|
|
statuscode = 200
|
|
response = "Client data fetched"
|
|
data = info
|
|
return 1
|
|
else
|
|
statuscode = 449
|
|
response = "Multiple Matches found"
|
|
data = null
|
|
return 1
|
|
|
|
//Get Server Status
|
|
/datum/topic_command/get_serverstatus
|
|
name = "get_serverstatus"
|
|
description = "Gets the serverstatus"
|
|
/datum/topic_command/get_serverstatus/run_command(queryparams)
|
|
var/list/s[] = list()
|
|
s["version"] = game_version
|
|
s["mode"] = master_mode
|
|
s["respawn"] = config.abandon_allowed
|
|
s["enter"] = config.enter_allowed
|
|
s["vote"] = config.allow_vote_mode
|
|
s["ai"] = config.allow_ai
|
|
s["host"] = host ? host : null
|
|
s["players"] = 0
|
|
s["stationtime"] = worldtime2text()
|
|
s["roundduration"] = round_duration()
|
|
s["gameid"] = game_id
|
|
|
|
if(queryparams["status"] == "2")
|
|
var/list/players = list()
|
|
var/list/admins = list()
|
|
|
|
for(var/client/C in clients)
|
|
if(C.holder)
|
|
if(C.holder.fakekey)
|
|
continue
|
|
admins[C.key] = C.holder.rank
|
|
players += C.key
|
|
|
|
s["players"] = players.len
|
|
s["playerlist"] = players
|
|
s["admins"] = admins.len
|
|
s["adminlist"] = admins
|
|
else
|
|
var/n = 0
|
|
var/admins = 0
|
|
|
|
for(var/client/C in clients)
|
|
if(C.holder)
|
|
if(C.holder.fakekey)
|
|
continue //so stealthmins aren't revealed by the hub
|
|
admins++
|
|
s["player[n]"] = C.key
|
|
n++
|
|
|
|
s["players"] = n
|
|
s["admins"] = admins
|
|
|
|
statuscode = 200
|
|
response = "Server Status fetched"
|
|
data = s
|
|
return 1
|
|
|
|
//Get a Staff List
|
|
/datum/topic_command/get_stafflist
|
|
name = "get_stafflist"
|
|
description = "Gets a list of connected staffmembers"
|
|
/datum/topic_command/get_stafflist/run_command(queryparams)
|
|
var/list/staff = list()
|
|
for (var/client/C in admins)
|
|
staff[C] = C.holder.rank
|
|
|
|
statuscode = 200
|
|
response = "Staff list fetched"
|
|
data = staff
|
|
return 1
|
|
|
|
//Grant Respawn
|
|
/datum/topic_command/grant_respawn
|
|
name = "grant_respawn"
|
|
description = "Grants a respawn to a specific target"
|
|
params = list(
|
|
"senderkey" = list("name"="senderkey","desc"="Unique id of the person that authorized the respawn","req"=1,"type"="senderkey"),
|
|
"target" = list("name"="target","desc"="Ckey of the target that should be granted a respawn","req"=1,"type"="str")
|
|
)
|
|
/datum/topic_command/grant_respawn/run_command(queryparams)
|
|
var/list/ghosts = get_ghosts(1,1)
|
|
var/target = queryparams["target"]
|
|
var/allow_antaghud = queryparams["allow_antaghud"]
|
|
var/senderkey = queryparams["senderkey"] //Identifier of the sender (Ckey / Userid / ...)
|
|
|
|
var/mob/dead/observer/G = ghosts[target]
|
|
|
|
if(!G in ghosts)
|
|
statuscode = 404
|
|
response = "Target not in ghosts list"
|
|
data = null
|
|
return 1
|
|
|
|
if(G.has_enabled_antagHUD && config.antag_hud_restricted && allow_antaghud == 0)
|
|
statuscode = 409
|
|
response = "Ghost has used Antag Hud - Respawn Aborted"
|
|
data = null
|
|
return 1
|
|
G.timeofdeath=-19999 /* time of death is checked in /mob/verb/abandon_mob() which is the Respawn verb.
|
|
timeofdeath is used for bodies on autopsy but since we're messing with a ghost I'm pretty sure
|
|
there won't be an autopsy.
|
|
*/
|
|
var/datum/preferences/P
|
|
|
|
if (G.client)
|
|
P = G.client.prefs
|
|
else if (G.ckey)
|
|
P = preferences_datums[G.ckey]
|
|
else
|
|
statuscode = 500
|
|
response = "Something went wrong, couldn't find the target's preferences datum"
|
|
data = null
|
|
return 1
|
|
|
|
for (var/entry in P.time_of_death)//Set all the prefs' times of death to a huge negative value so any respawn timers will be fine
|
|
P.time_of_death[entry] = -99999
|
|
|
|
G.has_enabled_antagHUD = 2
|
|
G.can_reenter_corpse = 1
|
|
|
|
G:show_message(text("<span class='notice'><B>You may now respawn. You should roleplay as if you learned nothing about the round during your time with the dead.</B></span>"), 1)
|
|
log_admin("[senderkey] allowed [key_name(G)] to bypass the 30 minute respawn limit via the API",ckey=key_name(G),admin_key=senderkey)
|
|
message_admins("Admin [senderkey] allowed [key_name_admin(G)] to bypass the 30 minute respawn limit via the API", 1)
|
|
|
|
|
|
statuscode = 200
|
|
response = "Respawn Granted"
|
|
data = null
|
|
return 1
|
|
|
|
//Ping Test
|
|
/datum/topic_command/ping
|
|
name = "ping"
|
|
description = "API test command"
|
|
/datum/topic_command/ping/run_command(queryparams)
|
|
var/x = 1
|
|
for (var/client/C)
|
|
x++
|
|
statuscode = 200
|
|
response = "Pong"
|
|
data = x
|
|
return 1
|
|
|
|
//Restart Round
|
|
/datum/topic_command/restart_round
|
|
name = "restart_round"
|
|
description = "Restarts the round"
|
|
params = list(
|
|
"senderkey" = list("name"="senderkey","desc"="Unique id of the person that authorized the restart","req"=1,"type"="senderkey")
|
|
)
|
|
/datum/topic_command/restart_round/run_command(queryparams)
|
|
var/senderkey = sanitize(queryparams["senderkey"]) //Identifier of the sender (Ckey / Userid / ...)
|
|
|
|
world << "<font size=4 color='#ff2222'>Server restarting by remote command.</font>"
|
|
log_and_message_admins("World restart initiated remotely by [senderkey].")
|
|
feedback_set_details("end_error","remote restart")
|
|
|
|
spawn(50)
|
|
log_game("Rebooting due to remote command.")
|
|
world.Reboot(10)
|
|
|
|
statuscode = 200
|
|
response = "Restart Command accepted"
|
|
data = null
|
|
return 1
|
|
|
|
//Get available Fax Machines
|
|
/datum/topic_command/send_adminmsg
|
|
name = "send_adminmsg"
|
|
description = "Sends a adminmessage to a player"
|
|
params = list(
|
|
"ckey" = list("name"="ckey","desc"="The target of the adminmessage","req"=1,"type"="str"),
|
|
"msg" = list("name"="msg","desc"="The message that should be sent","req"=1,"type"="str"),
|
|
"senderkey" = list("name"="senderkey","desc"="Unique id of the person that sent the adminmessage","req"=1,"type"="senderkey"),
|
|
"rank" = list("name"="rank","desc"="The rank that should be displayed - Defaults to admin if none specified","req"=0,"type"="str")
|
|
)
|
|
|
|
/datum/topic_command/send_adminmsg/run_command(queryparams)
|
|
/*
|
|
We got an adminmsg from IRC bot lets split the API
|
|
expected output:
|
|
1. ckey = ckey of person the message is to
|
|
2. msg = contents of message, parems2list requires
|
|
3. rank = Rank that should be displayed
|
|
4. senderkey = the ircnick that send the message.
|
|
*/
|
|
|
|
var/client/C
|
|
var/req_ckey = ckey(queryparams["ckey"])
|
|
|
|
for(var/client/K in clients)
|
|
if(K.ckey == req_ckey)
|
|
C = K
|
|
break
|
|
if(!C)
|
|
statuscode = 404
|
|
response = "No client with that name on server"
|
|
data = null
|
|
return 1
|
|
|
|
var/rank = queryparams["rank"]
|
|
if(!rank)
|
|
rank = "Admin"
|
|
|
|
var/message = "<font color='red'>[rank] PM from <b><a href='?discord_msg=[queryparams["senderkey"]]'>[queryparams["senderkey"]]</a></b>: [queryparams["msg"]]</font>"
|
|
var/amessage = "<font color='blue'>[rank] PM from <a href='?discord_msg=[queryparams["senderkey"]]'>[queryparams["senderkey"]]</a> to <b>[key_name(C, highlight_special = 1)]</b> : [queryparams["msg"]]</font>"
|
|
|
|
C.received_discord_pm = world.time
|
|
C.discord_admin = queryparams["senderkey"]
|
|
|
|
C << 'sound/effects/adminhelp.ogg'
|
|
C << message
|
|
|
|
for(var/client/A in admins)
|
|
if(A != C)
|
|
A << amessage
|
|
|
|
|
|
statuscode = 200
|
|
response = "Admin Message sent"
|
|
data = null
|
|
return 1
|
|
|
|
//Send a Command Report
|
|
/datum/topic_command/send_commandreport
|
|
name = "send_commandreport"
|
|
description = "Sends a command report"
|
|
params = list(
|
|
"senderkey" = list("name"="senderkey","desc"="Unique id of the person that sent the commandreport","req"=1,"type"="senderkey"),
|
|
"title" = list("name"="title","desc"="The message title that should be sent, Defaults to NanoTrasen Update if not specified","req"=0,"type"="str"),
|
|
"body" = list("name"="body","desc"="The message body that should be sent","req"=1,"type"="str"),
|
|
"type" = list("name"="type","desc"="The type of the message that should be sent, Defaults to freeform","req"=0,"type"="slct","options"=list("freeform","ccia")),
|
|
"sendername" = list("name"="sendername","desc"="IC Name of the sender for the CCIA Report, Defaults to CCIAAMS, \[Command-StationName\]","req"=0,"type"="string"),
|
|
"announce" = list("name"="announce","desc"="If the report should be announce 1 -> Yes, 0 -> No, Defaults to 1","req"=0,"type"="int")
|
|
)
|
|
/datum/topic_command/send_commandreport/run_command(queryparams)
|
|
var/senderkey = sanitize(queryparams["senderkey"]) //Identifier of the sender (Ckey / Userid / ...)
|
|
var/reporttitle = sanitizeSafe(queryparams["title"]) //Title of the report
|
|
var/reportbody = nl2br(sanitize(queryparams["body"],encode=0,extra=0,max_length=0)) //Body of the report
|
|
var/reporttype = queryparams["type"] //Type of the report: freeform / ccia / admin
|
|
var/reportsender = sanitizeSafe(queryparams["sendername"]) //Name of the sender
|
|
var/reportannounce = text2num(queryparams["announce"]) //Announce the contents report to the public: 1 / 0
|
|
|
|
if(!reporttitle)
|
|
reporttitle = "NanoTrasen Update"
|
|
if(!reporttype)
|
|
reporttype = "freeform"
|
|
if(!reportannounce)
|
|
reportannounce = 1
|
|
|
|
//Set the report footer for CCIA Announcements
|
|
if (reporttype == "ccia")
|
|
if (reportsender)
|
|
reportbody += "<br><br>- [reportsender], Central Command Internal Affairs Agent, [commstation_name()]"
|
|
else
|
|
reportbody += "<br><br>- CCIAAMS, [commstation_name()]"
|
|
|
|
//Send the message to the communications consoles
|
|
post_comm_message(reporttitle, reportbody)
|
|
|
|
if(reportannounce == 1)
|
|
command_announcement.Announce(reportbody, reporttitle, new_sound = 'sound/AI/commandreport.ogg', do_newscast = 1, msg_sanitized = 1);
|
|
if(reportannounce == 0)
|
|
world << "<span class='alert'>New NanoTrasen Update available at all communication consoles.</span>"
|
|
world << sound('sound/AI/commandreport.ogg')
|
|
|
|
|
|
log_admin("[senderkey] has created a command report via the api: [reportbody]",admin_key=senderkey)
|
|
message_admins("[senderkey] has created a command report via the api", 1)
|
|
|
|
statuscode = 200
|
|
response = "Command Report sent"
|
|
data = null
|
|
return 1
|
|
|
|
//Send Fax
|
|
/datum/topic_command/send_fax
|
|
name = "send_fax"
|
|
description = "Sends a fax"
|
|
params = list(
|
|
"senderkey" = list("name"="senderkey","desc"="Unique id of the person that sent the fax","req"=1,"type"="senderkey"),
|
|
"title" = list("name"="title","desc"="The message title that should be sent","req"=1,"type"="str"),
|
|
"body" = list("name"="body","desc"="The message body that should be sent","req"=1,"type"="str"),
|
|
"target" = list("name"="target","desc"="The target faxmachines the fax should be sent to","req"=1,"type"="lst")
|
|
)
|
|
/datum/topic_command/send_fax/run_command(queryparams)
|
|
var/list/responselist = list()
|
|
var/list/sendsuccess = list()
|
|
var/list/targetlist = queryparams["target"] //Target locations where the fax should be sent to
|
|
var/senderkey = sanitize(queryparams["senderkey"]) //Identifier of the sender (Ckey / Userid / ...)
|
|
var/faxtitle = sanitizeSafe(queryparams["title"]) //Title of the report
|
|
var/faxbody = sanitize(queryparams["body"],max_length=0) //Body of the report
|
|
var/faxannounce = text2num(queryparams["announce"]) //Announce the contents report to the public: 1 / 0
|
|
|
|
if(!targetlist || targetlist.len < 1)
|
|
statuscode = 400
|
|
response = "Parameter target not set"
|
|
data = null
|
|
return 1
|
|
|
|
var/sendresult = 0
|
|
|
|
//Send the fax
|
|
for (var/obj/machinery/photocopier/faxmachine/F in allfaxes)
|
|
if (F.department in targetlist)
|
|
sendresult = send_fax(F, faxtitle, faxbody, senderkey)
|
|
if (sendresult == 1)
|
|
sendsuccess.Add(F.department)
|
|
responselist[F.department] = "success"
|
|
else
|
|
responselist[F.department] = "failed"
|
|
|
|
//Announce that the fax has been sent
|
|
if(faxannounce == 1)
|
|
if(sendsuccess.len < 1)
|
|
command_announcement.Announce("A fax message from Central Command could not be delivered because all of the following fax machines are inoperational: <br>"+list2text(targetlist, ", "), "Fax Received", new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1);
|
|
else
|
|
command_announcement.Announce("A fax message from Central Command has been sent to the following fax machines: <br>"+list2text(sendsuccess, ", "), "Fax Received", new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1);
|
|
|
|
log_admin("[senderkey] sent a fax via the API: : [faxbody]",admin_key=senderkey)
|
|
message_admins("[senderkey] sent a fax via the API", 1)
|
|
|
|
statuscode = 200
|
|
response = "Fax sent"
|
|
data = responselist
|
|
return 1
|
|
|
|
/datum/topic_command/send_fax/proc/send_fax(var/obj/machinery/photocopier/faxmachine/F, title, body, senderkey)
|
|
// Create the reply message
|
|
var/obj/item/weapon/paper/P = new /obj/item/weapon/paper( null ) //hopefully the null loc won't cause trouble for us
|
|
P.name = "[current_map.boss_name] - [title]"
|
|
P.info = body
|
|
P.update_icon()
|
|
|
|
// Stamps
|
|
var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
|
|
stampoverlay.icon_state = "paper_stamp-cent"
|
|
if(!P.stamped)
|
|
P.stamped = new
|
|
P.stamped += /obj/item/weapon/stamp
|
|
P.overlays += stampoverlay
|
|
P.stamps += "<HR><i>This paper has been stamped by the Central Command Quantum Relay.</i>"
|
|
|
|
if(F.recievefax(P))
|
|
log_and_message_admins("[senderkey] sent a fax message to the [F.department] fax machine via the api. (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[F.x];Y=[F.y];Z=[F.z]'>JMP</a>)")
|
|
sent_faxes += P
|
|
return 1
|
|
else
|
|
qdel(P)
|
|
return 2
|
|
|
|
// Update discord_bot's channels.
|
|
/datum/topic_command/update_bot_channels
|
|
name = "update_bot_channels"
|
|
description = "Tells the ingame instance of the Discord bot to update its cached channels list."
|
|
|
|
/datum/topic_command/update_bot_channels/run_command()
|
|
data = null
|
|
|
|
if (!discord_bot)
|
|
statuscode = 404
|
|
response = "Ingame Discord bot not initialized."
|
|
return 1
|
|
|
|
switch (discord_bot.update_channels())
|
|
if (1)
|
|
statuscode = 404
|
|
response = "Ingame Discord bot is not active."
|
|
if (2)
|
|
statuscode = 500
|
|
response = "Ingame Discord bot encountered error attempting to access database."
|
|
else
|
|
statuscode = 200
|
|
response = "Ingame Discord bot's channels were successfully updated."
|
|
|
|
return 1
|