mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-01-03 14:02:49 +00:00
Rewrite API (#825)
he /world/Topic() API has been rewritten. General function: The API is initialized upon roundstart and generates a list of possible api requests from /code/datums/api.dm Once a request is made the following checks are performed: If a query parameter is set (the function that should be called) If the ip is ratelimited (or rate-limit-whitelisted) The query is authenticated against the db using ip, function and key If these checks pass, it is validated that the api command exists Then the api command is called, all supplied params are passed through Each API function returns 1 no matter if it failed or succeded Additional info is provided in the statuscode, response and data vars The statuscode, response and data are send back to the caller (through list2params) The data var is json encoded before sending it back This is not backward compatible to the current API Current API Clients need to be updated
This commit is contained in:
@@ -19,6 +19,41 @@ CREATE TABLE `ss13_admin_log` (
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `ss13_api_functions` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`function` VARCHAR(50) NULL DEFAULT '' COLLATE 'utf8_bin',
|
||||
`description` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
|
||||
PRIMARY KEY (`id`)
|
||||
)
|
||||
COLLATE='utf8_bin'
|
||||
ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE `ss13_api_tokens` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`token` VARCHAR(100) NOT NULL COLLATE 'utf8_bin',
|
||||
`ip` VARCHAR(16) NULL DEFAULT NULL COLLATE 'utf8_bin',
|
||||
`creator` VARCHAR(50) NOT NULL COLLATE 'utf8_bin',
|
||||
`description` VARCHAR(100) NOT NULL COLLATE 'utf8_bin',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted_at` DATETIME NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
)
|
||||
COLLATE='utf8_bin'
|
||||
ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE `ss13_api_token_function` (
|
||||
`function_id` INT(11) NOT NULL,
|
||||
`token_id` INT(11) NOT NULL,
|
||||
PRIMARY KEY (`function_id`, `token_id`),
|
||||
INDEX `token_id` (`token_id`),
|
||||
CONSTRAINT `function_id` FOREIGN KEY (`function_id`) REFERENCES `ss13_api_functions` (`id`) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT `token_id` FOREIGN KEY (`token_id`) REFERENCES `ss13_api_tokens` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
|
||||
)
|
||||
COLLATE='utf8_bin'
|
||||
ENGINE=InnoDB;
|
||||
|
||||
|
||||
CREATE TABLE `ss13_ban` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`bantime` datetime NOT NULL,
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
#include "code\controllers\ProcessScheduler\core\processScheduler.dm"
|
||||
#include "code\datums\ai_law_sets.dm"
|
||||
#include "code\datums\ai_laws.dm"
|
||||
#include "code\datums\api.dm"
|
||||
#include "code\datums\browser.dm"
|
||||
#include "code\datums\computerfiles.dm"
|
||||
#include "code\datums\crew.dm"
|
||||
|
||||
@@ -11,6 +11,7 @@ var/global/list/human_mob_list = list() //List of all human mobs and sub-type
|
||||
var/global/list/silicon_mob_list = list() //List of all silicon mobs, including clientless
|
||||
var/global/list/living_mob_list = list() //List of all alive mobs, including clientless. Excludes /mob/new_player
|
||||
var/global/list/dead_mob_list = list() //List of all dead mobs, including clientless. Excludes /mob/new_player
|
||||
var/global/list/topic_commands = list() //List of all API commands available
|
||||
|
||||
var/global/list/cable_list = list() //Index for all cables, so that powernets don't have to look through the entire world all the time
|
||||
var/global/list/chemical_reactions_list //list of all /datum/chemical_reaction datums. Used during chemical reactions
|
||||
|
||||
@@ -230,6 +230,10 @@ var/list/gamemode_cache = list()
|
||||
//AUG2016
|
||||
var/antag_contest_enabled = 0
|
||||
|
||||
//API Rate limiting
|
||||
var/api_rate_limit = 50
|
||||
var/list/api_rate_limit_whitelist = list()
|
||||
|
||||
/datum/configuration/New()
|
||||
var/list/L = typesof(/datum/game_mode) - /datum/game_mode
|
||||
for (var/T in L)
|
||||
@@ -729,6 +733,12 @@ var/list/gamemode_cache = list()
|
||||
if("antag_contest_enabled")
|
||||
config.antag_contest_enabled = 1
|
||||
|
||||
if("api_rate_limit")
|
||||
config.api_rate_limit = text2num(value)
|
||||
|
||||
if("api_rate_limit_whitelist")
|
||||
config.api_rate_limit_whitelist = text2list(value, ";")
|
||||
|
||||
else
|
||||
log_misc("Unknown setting in configuration: '[name]'")
|
||||
|
||||
|
||||
712
code/datums/api.dm
Normal file
712
code/datums/api.dm
Normal file
@@ -0,0 +1,712 @@
|
||||
//
|
||||
// This file contains the API functions for the serverside API
|
||||
//
|
||||
|
||||
//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()
|
||||
topic_commands[A.name] = A
|
||||
|
||||
return 1
|
||||
|
||||
//API Boilerplate
|
||||
/datum/topic_command
|
||||
var/name = null
|
||||
var/list/required_params = list() //Required Parameters for the command
|
||||
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/param in required_params)
|
||||
if(queryparams[param] == null)
|
||||
errorcount ++
|
||||
missing_params += param
|
||||
|
||||
if(errorcount)
|
||||
statuscode = 400
|
||||
response = "Required params missing"
|
||||
data = missing_params
|
||||
return errorcount
|
||||
|
||||
//Char Names
|
||||
/datum/topic_command/get_char_list
|
||||
name = "get_char_list"
|
||||
/datum/topic_command/get_char_list/run_command(queryparams)
|
||||
if (!ticker)
|
||||
statuscode = 500
|
||||
response = "Game not started yet!"
|
||||
return 1
|
||||
|
||||
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"
|
||||
/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_ccia
|
||||
name = "get_count_ccia"
|
||||
/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"
|
||||
/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"
|
||||
/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"
|
||||
/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"
|
||||
required_params = list("faxtype") //Type of the faxes to be retrieved (sent / received)
|
||||
/datum/topic_command/get_faxlist/run_command(queryparams)
|
||||
if (!ticker)
|
||||
statuscode = 500
|
||||
response = "Round hasn't started yet! No faxes to display!"
|
||||
data = null
|
||||
return 1
|
||||
|
||||
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"
|
||||
required_params = list("faxtype","faxid")
|
||||
/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"
|
||||
/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"
|
||||
/datum/topic_command/get_manifest/run_command(queryparams)
|
||||
if (!ticker)
|
||||
statuscode = 500
|
||||
response = "Game not started yet!"
|
||||
return 1
|
||||
|
||||
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"
|
||||
/datum/topic_command/get_player_list/run_command(queryparams)
|
||||
var/list/players = list()
|
||||
for (var/client/C in clients)
|
||||
players += C.key
|
||||
|
||||
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"
|
||||
required_params = list("search") //search --> list with data to search for
|
||||
/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"
|
||||
/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()
|
||||
|
||||
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"] = list2params(players)
|
||||
s["admins"] = admins.len
|
||||
s["adminlist"] = list2params(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"
|
||||
/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"
|
||||
required_params = list("senderkey","target")
|
||||
/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("\blue <B>You may now respawn. You should roleplay as if you learned nothing about the round during your time with the dead.</B>"), 1)
|
||||
log_admin("[senderkey] allowed [key_name(G)] to bypass the 30 minute respawn limit via the API")
|
||||
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"
|
||||
/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"
|
||||
required_params = list("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")
|
||||
|
||||
if (blackbox)
|
||||
blackbox.save_all_data_to_sql()
|
||||
|
||||
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"
|
||||
required_params = list("ckey","msg","senderkey")
|
||||
/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)]</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"
|
||||
required_params = list("senderkey","body")
|
||||
/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 = sanitize(queryparams["body"]) //Body of the report
|
||||
var/reporttype = queryparams["type"] //Type of the report: freeform / ccia / admin
|
||||
var/reportsender = sanitize(queryparams["sendername"]) //Name of the sender
|
||||
var/reportannounce = queryparams["announce"] //Announce the contents report to the public: 1 / 0
|
||||
|
||||
if(!reporttitle)
|
||||
reporttitle = "NanoTrasen Update"
|
||||
if(!reporttype)
|
||||
reporttype = "freeform"
|
||||
if(!reportannounce)
|
||||
reportannounce = 1
|
||||
|
||||
//Send the message to the communications consoles
|
||||
for (var/obj/machinery/computer/communications/C in machines)
|
||||
if(! (C.stat & (BROKEN|NOPOWER) ) )
|
||||
var/obj/item/weapon/paper/P = new /obj/item/weapon/paper( C.loc )
|
||||
P.name = "[command_name()] Update"
|
||||
P.info = replacetext(reportbody, "\n", "<br/>")
|
||||
P.update_space(P.info)
|
||||
P.update_icon()
|
||||
C.messagetitle.Add("[command_name()] Update")
|
||||
C.messagetext.Add(P.info)
|
||||
|
||||
//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()]"
|
||||
|
||||
if(reportannounce == 1)
|
||||
command_announcement.Announce(reportbody, reporttitle, new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1);
|
||||
if(reportannounce == 0)
|
||||
world << "\red New NanoTrasen Update available at all communication consoles."
|
||||
world << sound('sound/AI/commandreport.ogg')
|
||||
|
||||
|
||||
log_admin("[senderkey] has created a command report via the api: [reportbody]")
|
||||
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"
|
||||
required_params = list("target","senderkey","title","body")
|
||||
/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"]) //Body of the report
|
||||
var/faxsender = sanitize(queryparams["sendername"]) //Name of the sender
|
||||
var/faxannounce = 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
|
||||
if(!faxannounce)
|
||||
faxannounce = 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("[faxsender] sent a fax via the API: : [faxbody]")
|
||||
message_admins("[faxsender] 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 = "[command_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
|
||||
@@ -229,20 +229,20 @@ Allow admins to set players to be able to respawn/bypass 30 min wait, without th
|
||||
Ccomp's first proc.
|
||||
*/
|
||||
|
||||
/client/proc/get_ghosts(var/notify = 0,var/what = 2)
|
||||
proc/get_ghosts(var/notify = 0,var/what = 2, var/client/C = null)
|
||||
// what = 1, return ghosts ass list.
|
||||
// what = 2, return mob list
|
||||
|
||||
var/list/mobs = list()
|
||||
var/list/ghosts = list()
|
||||
var/list/sortmob = sortAtom(mob_list) // get the mob list.
|
||||
/var/any=0
|
||||
var/any=0
|
||||
for(var/mob/dead/observer/M in sortmob)
|
||||
mobs.Add(M) //filter it where it's only ghosts
|
||||
any = 1 //if no ghosts show up, any will just be 0
|
||||
if(!any)
|
||||
if(notify)
|
||||
src << "There doesn't appear to be any ghosts for you to select."
|
||||
if(notify && C)
|
||||
C << "There doesn't appear to be any ghosts for you to select."
|
||||
return
|
||||
|
||||
for(var/mob/M in mobs)
|
||||
@@ -260,7 +260,7 @@ Ccomp's first proc.
|
||||
set desc = "Let's the player bypass the 30 minute wait to respawn or allow them to re-enter their corpse."
|
||||
if(!holder)
|
||||
src << "Only administrators may use this command."
|
||||
var/list/ghosts= get_ghosts(1,1)
|
||||
var/list/ghosts = get_ghosts(1,1,src)
|
||||
|
||||
var/target = input("Please, select a ghost!", "COME BACK TO LIFE!", null, null) as null|anything in ghosts
|
||||
if(!target)
|
||||
|
||||
@@ -193,24 +193,23 @@ var/list/sent_faxes = list() //cache for faxes that have been sent by the admins
|
||||
if(department == "Unknown")
|
||||
return 0 //You can't send faxes to "Unknown"
|
||||
|
||||
if (!istype(incoming, /obj/item/weapon/paper) && !istype(incoming, /obj/item/weapon/photo) && !istype(incoming, /obj/item/weapon/paper_bundle))
|
||||
return 0
|
||||
|
||||
flick("faxreceive", src)
|
||||
playsound(loc, "sound/items/polaroid1.ogg", 50, 1)
|
||||
|
||||
// give the sprite some time to flick
|
||||
sleep(20)
|
||||
spawn(20)
|
||||
if (istype(incoming, /obj/item/weapon/paper))
|
||||
copy(incoming)
|
||||
else if (istype(incoming, /obj/item/weapon/photo))
|
||||
photocopy(incoming)
|
||||
else if (istype(incoming, /obj/item/weapon/paper_bundle))
|
||||
bundlecopy(incoming)
|
||||
do_pda_alerts()
|
||||
use_power(active_power_usage)
|
||||
|
||||
if (istype(incoming, /obj/item/weapon/paper))
|
||||
copy(incoming)
|
||||
else if (istype(incoming, /obj/item/weapon/photo))
|
||||
photocopy(incoming)
|
||||
else if (istype(incoming, /obj/item/weapon/paper_bundle))
|
||||
bundlecopy(incoming)
|
||||
else
|
||||
return 0
|
||||
|
||||
do_pda_alerts()
|
||||
|
||||
use_power(active_power_usage)
|
||||
return 1
|
||||
|
||||
/obj/machinery/photocopier/faxmachine/proc/send_admin_fax(var/mob/sender, var/destination)
|
||||
|
||||
506
code/world.dm
506
code/world.dm
@@ -108,414 +108,65 @@ var/global/datum/global_init/init = new ()
|
||||
|
||||
return
|
||||
|
||||
var/world_topic_spam_protect_ip = "0.0.0.0"
|
||||
var/world_topic_spam_protect_time = world.timeofday
|
||||
var/list/world_api_rate_limit = list()
|
||||
|
||||
/world/Topic(T, addr, master, key)
|
||||
diary << "TOPIC: \"[T]\", from:[addr], master:[master], key:[key][log_end]"
|
||||
var/list/response[] = list()
|
||||
var/list/queryparams[] = json_decode(T)
|
||||
var/query = queryparams["query"]
|
||||
var/auth = queryparams["auth"]
|
||||
log_debug("API: Request Received - from:[addr], master:[master], key:[key]")
|
||||
diary << "TOPIC: \"[T]\", from:[addr], master:[master], key:[key], auth:[auth] [log_end]"
|
||||
|
||||
if (T == "ping")
|
||||
var/x = 1
|
||||
for (var/client/C)
|
||||
x++
|
||||
return x
|
||||
if (isnull(query))
|
||||
log_debug("API - Bad Request - No query specified")
|
||||
response["statuscode"] = 400
|
||||
response["response"] = "Bad Request - No query specified"
|
||||
return json_encode(response)
|
||||
|
||||
else if(T == "players")
|
||||
var/n = 0
|
||||
for(var/mob/M in player_list)
|
||||
if(M.client)
|
||||
n++
|
||||
return n
|
||||
|
||||
else if (T == "admins")
|
||||
var/n = 0
|
||||
for (var/client/client in clients)
|
||||
if (client.holder && client.holder.rights & (R_MOD|R_ADMIN))
|
||||
n++
|
||||
|
||||
return n
|
||||
|
||||
else if (T == "cciaa")
|
||||
var/n = 0
|
||||
for (var/client/client in clients)
|
||||
if (client.holder && (client.holder.rights & R_CCIAA) && !(client.holder.rights & R_ADMIN))
|
||||
n++
|
||||
|
||||
return n
|
||||
|
||||
else if (T == "gamemode")
|
||||
return master_mode
|
||||
|
||||
else if (T == "who")
|
||||
var/list/players = list()
|
||||
for (var/client/C in clients)
|
||||
players += C.key
|
||||
|
||||
return list2params(players)
|
||||
|
||||
else if (copytext(T,1,7) == "status")
|
||||
var/input[] = params2list(T)
|
||||
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
|
||||
|
||||
// This is dumb, but spacestation13.com's banners break if player count isn't the 8th field of the reply, so... this has to go here.
|
||||
s["players"] = 0
|
||||
s["stationtime"] = worldtime2text()
|
||||
s["roundduration"] = round_duration()
|
||||
|
||||
if(input["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"] = list2params(players)
|
||||
s["admins"] = admins.len
|
||||
s["adminlist"] = list2params(admins)
|
||||
var/unauthed = do_auth_check(addr,auth,query)
|
||||
if (unauthed)
|
||||
if (unauthed == 3)
|
||||
log_debug("API: Request denied - Auth Service Unavailable")
|
||||
response["statuscode"] = 503
|
||||
response["response"] = "Auth Service Unavailable"
|
||||
return json_encode(response)
|
||||
else if (unauthed == 2)
|
||||
log_debug("API: Request denied - Throttled")
|
||||
response["statuscode"] = 429
|
||||
response["response"] = "Throttled"
|
||||
return json_encode(response)
|
||||
else
|
||||
var/n = 0
|
||||
var/admins = 0
|
||||
log_debug("API: Request denied - Bad Auth")
|
||||
response["statuscode"] = 401
|
||||
response["response"] = "Bad Auth"
|
||||
return json_encode(response)
|
||||
|
||||
|
||||
|
||||
log_debug("API: Auth valid")
|
||||
var/datum/topic_command/command = topic_commands[query]
|
||||
|
||||
if (isnull(command))
|
||||
log_debug("API: Unknown command called: [query]")
|
||||
response["statuscode"] = 501
|
||||
response["response"] = "Not Implemented"
|
||||
return json_encode(response)
|
||||
|
||||
if(command.check_params_missing(queryparams))
|
||||
log_debug("API: Mising Params - Status: [command.statuscode] - Response: [command.response]")
|
||||
response["statuscode"] = command.statuscode
|
||||
response["response"] = command.response
|
||||
response["data"] = command.data
|
||||
return json_encode(response)
|
||||
else
|
||||
command.run_command(queryparams)
|
||||
log_debug("API: Function called: [query] - Status: [command.statuscode] - Response: [command.response]")
|
||||
response["statuscode"] = command.statuscode
|
||||
response["response"] = command.response
|
||||
response["data"] = command.data
|
||||
return json_encode(response)
|
||||
|
||||
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
|
||||
|
||||
return list2params(s)
|
||||
|
||||
else if(T == "manifest")
|
||||
if (!ticker)
|
||||
return "Game not started yet!"
|
||||
|
||||
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"
|
||||
|
||||
return list2params(positions)
|
||||
|
||||
else if(copytext(T,1,5) == "mute")
|
||||
var/input[] = params2list(T)
|
||||
var/bad_key = do_topic_spam_protection(addr, input["key"])
|
||||
|
||||
if (bad_key)
|
||||
return bad_key
|
||||
|
||||
for (var/client/C in clients)
|
||||
if (C.ckey == ckey(input["mute"]))
|
||||
C.mute_discord = !C.mute_discord
|
||||
|
||||
switch (C.mute_discord)
|
||||
if (1)
|
||||
C << "<b><font color='red'>You have been muted from replying to Discord PMs by [input["admin"]]!</font></b>"
|
||||
log_and_message_admins("[C] has been muted from Discord PMs by [input["admin"]].")
|
||||
return "[C.key] is now muted from replying to Discord PMs."
|
||||
if (0)
|
||||
C << "<b><font color='red'>You have been unmuted from replying to Discord PMs by [input["admin"]]!</font></b>"
|
||||
log_and_message_admins("[C] has been unmuted from Discord PMs by [input["admin"]].")
|
||||
return "[C.key] is now unmuted from replying to Discord PMs."
|
||||
|
||||
return "I couldn't find that ckey!"
|
||||
|
||||
else if(copytext(T,1,5) == "info")
|
||||
var/input[] = params2list(T)
|
||||
var/bad_key = do_topic_spam_protection(addr, input["key"])
|
||||
|
||||
if (bad_key)
|
||||
return bad_key
|
||||
|
||||
var/list/search = params2list(input["info"])
|
||||
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)
|
||||
return "No matches"
|
||||
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
|
||||
return list2params(info)
|
||||
else
|
||||
return "Multiple matches found!"
|
||||
|
||||
else if(copytext(T,1,9) == "adminmsg")
|
||||
/*
|
||||
We got an adminmsg from IRC bot lets split the input then validate the input.
|
||||
expected output:
|
||||
1. adminmsg = ckey of person the message is to
|
||||
2. msg = contents of message, parems2list requires
|
||||
3. validatationkey = the key the bot has, it should match the gameservers commspassword in it's configuration.
|
||||
4. sender = the ircnick that send the message.
|
||||
*/
|
||||
|
||||
|
||||
var/input[] = params2list(T)
|
||||
var/bad_key = do_topic_spam_protection(addr, input["key"])
|
||||
|
||||
if (bad_key)
|
||||
return bad_key
|
||||
|
||||
var/client/C
|
||||
var/req_ckey = ckey(input["adminmsg"])
|
||||
|
||||
for(var/client/K in clients)
|
||||
if(K.ckey == req_ckey)
|
||||
C = K
|
||||
break
|
||||
if(!C)
|
||||
return "No client with that name on server"
|
||||
|
||||
var/rank = input["rank"]
|
||||
if(!rank)
|
||||
rank = "Admin"
|
||||
|
||||
var/message = "<font color='red'>Discord-[rank] PM from <b><a href='?discord_msg=[input["sender"]]'>Discord-[input["sender"]]</a></b>: [input["msg"]]</font>"
|
||||
var/amessage = "<font color='blue'>Discord-[rank] PM from <a href='?discord_msg=[input["sender"]]'>Discord-[input["sender"]]</a> to <b>[key_name(C)]</b> : [input["msg"]]</font>"
|
||||
|
||||
C.received_discord_pm = world.time
|
||||
C.discord_admin = input["sender"]
|
||||
|
||||
C << 'sound/effects/adminhelp.ogg'
|
||||
C << message
|
||||
|
||||
|
||||
for(var/client/A in admins)
|
||||
if(A != C)
|
||||
A << amessage
|
||||
|
||||
return "Message Successful"
|
||||
|
||||
else if(copytext(T,1,6) == "notes")
|
||||
var/input[] = params2list(T)
|
||||
var/bad_key = do_topic_spam_protection(addr, input["key"])
|
||||
|
||||
if (bad_key)
|
||||
return bad_key
|
||||
|
||||
return show_player_info_discord(ckey(input["notes"]))
|
||||
|
||||
else if(copytext(T,1,4) == "age")
|
||||
var/input[] = params2list(T)
|
||||
var/bad_key = do_topic_spam_protection(addr, input["key"])
|
||||
|
||||
if (bad_key)
|
||||
return bad_key
|
||||
|
||||
var/age = get_player_age(input["age"])
|
||||
if(isnum(age))
|
||||
if(age >= 0)
|
||||
return "[age]"
|
||||
else
|
||||
return "Ckey not found"
|
||||
else
|
||||
return "Database connection failed or not set up"
|
||||
|
||||
else if (copytext(T, 1, 8) == "restart")
|
||||
var/input[] = params2list(T)
|
||||
var/bad_key = do_topic_spam_protection(addr, input["key"])
|
||||
|
||||
if (bad_key)
|
||||
log_and_message_admins("Remote restart attempted and stopped. Dumping topic call data.")
|
||||
log_and_message_admins("TOPIC: \"[T]\", from: [addr], master: [master], key: [key].")
|
||||
return bad_key
|
||||
|
||||
world << "<font size=4 color='#ff2222'>Server restarting by remote command.</font>"
|
||||
log_and_message_admins("World restart initiated remotely by [input["restart"]].")
|
||||
feedback_set_details("end_error","remote restart")
|
||||
|
||||
if (blackbox)
|
||||
blackbox.save_all_data_to_sql()
|
||||
|
||||
sleep(50)
|
||||
log_game("Rebooting due to remote command.")
|
||||
world.Reboot(2)
|
||||
|
||||
return "Server successfully restarted."
|
||||
|
||||
else if (copytext(T, 1, 9) == "announce")
|
||||
var/input[] = params2list(T)
|
||||
var/bad_key = do_topic_spam_protection(addr, input["key"])
|
||||
|
||||
if (bad_key)
|
||||
return bad_key
|
||||
|
||||
var/message = replacetext(input["msg"], "\n", "<br>")
|
||||
world << "<span class=notice><b>[input["announce"] ? input["announce"] : "Administrator"] Announces via Discord:</b><p style='text-indent: 50px'>[message]</p></span>"
|
||||
log_and_message_admins("[input["announce"]] announced remotely: [input["msg"]].")
|
||||
|
||||
return "Announcement successfully sent."
|
||||
|
||||
else if (copytext(T, 1, 8) == "faxlist")
|
||||
var/input[] = params2list(T)
|
||||
var/bad_key = do_topic_spam_protection(addr, input["key"])
|
||||
|
||||
if (bad_key)
|
||||
return bad_key
|
||||
|
||||
if (!ticker)
|
||||
return "Round hasn't started yet! No faxes to display!"
|
||||
|
||||
var/list/faxes = list()
|
||||
switch (input["faxlist"])
|
||||
if ("received")
|
||||
faxes = arrived_faxes
|
||||
if ("sent")
|
||||
faxes = sent_faxes
|
||||
|
||||
if (!faxes || !faxes.len)
|
||||
return "No faxes found!"
|
||||
|
||||
var/list/output = list()
|
||||
for (var/i = 1, i <= faxes.len, i++)
|
||||
var/obj/item/a = faxes[i]
|
||||
output += "ID [i]"
|
||||
output["ID [i]"] = a.name ? a.name : "Untitled Fax"
|
||||
|
||||
return list2params(output)
|
||||
|
||||
else if (copytext(T, 1, 7) == "getfax")
|
||||
var/input[] = params2list(T)
|
||||
var/bad_key = do_topic_spam_protection(addr, input["key"])
|
||||
|
||||
if (bad_key)
|
||||
return bad_key
|
||||
|
||||
var/list/faxes = list()
|
||||
switch (input["received"])
|
||||
if ("received")
|
||||
faxes = arrived_faxes
|
||||
if ("sent")
|
||||
faxes = sent_faxes
|
||||
|
||||
if (!faxes || !faxes.len)
|
||||
return "No faxes found!"
|
||||
|
||||
var/fax_id = text2num(input["getfax"])
|
||||
if (fax_id > faxes.len || fax_id < 1)
|
||||
return "Invalid fax ID!"
|
||||
|
||||
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
|
||||
|
||||
return list2params(output)
|
||||
else if (istype(faxes[fax_id], /obj/item/weapon/photo))
|
||||
return "The fax is a photo. I cannot send images, unfortunately..."
|
||||
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)
|
||||
return "The bundle was empty! How is that even possible?"
|
||||
|
||||
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"
|
||||
|
||||
return list2params(output)
|
||||
|
||||
return "Unable to recognize the fax type. Cannot send contents!"
|
||||
|
||||
/world/Reboot(var/reason)
|
||||
/*spawn(0)
|
||||
@@ -754,20 +405,41 @@ var/world_topic_spam_protect_time = world.timeofday
|
||||
|
||||
#undef FAILED_DB_CONNECTION_CUTOFF
|
||||
|
||||
/world/proc/do_topic_spam_protection(var/addr, var/key)
|
||||
if (!config.comms_password || config.comms_password == "")
|
||||
return "No comms password configured, aborting."
|
||||
/world/proc/do_auth_check(var/addr, var/auth, var/function)
|
||||
//Check if rate limited
|
||||
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
|
||||
|
||||
if (key == config.comms_password)
|
||||
return 0
|
||||
world_api_rate_limit[addr] = world.time // Set the time of the last request
|
||||
|
||||
//Then query for auth
|
||||
if (!establish_db_connection(dbcon))
|
||||
return 3 //DB Unavailable
|
||||
|
||||
var/DBQuery/authquery = dbcon.NewQuery({"SELECT *
|
||||
FROM ss13_api_token_function as api_t_f, ss13_api_tokens as api_t, ss13_api_functions as api_f
|
||||
WHERE api_t.id = api_t_f.token_id AND api_f.id = api_t_f.function_id
|
||||
AND (
|
||||
(token = :token AND ip = :ip AND function = :function)
|
||||
OR
|
||||
(token = :token AND ip IS NULL AND function = :function)
|
||||
OR
|
||||
(token = :token AND ip = :ip AND function IS NULL)
|
||||
OR
|
||||
(token IS NULL AND ip = :ip AND function IS NULL)
|
||||
)"})
|
||||
//Get the tokens and the associated functions
|
||||
//Check if the token, the ip and the function matches OR
|
||||
// the token + function matches and the ip is NULL (Functions that can be used by any ip, but require a token)
|
||||
// the token + ip matches and the function is NULL (Allow a specific ip with a specific token to use all functions)
|
||||
// the token + ip is NULL and the function matches (Allow a specific function to be used without auth)
|
||||
|
||||
authquery.Execute(list(":token" = auth, ":ip" = addr, ":function" = function))
|
||||
log_debug("API: Auth Check - Query Executed - Returned Rows: [authquery.RowCount()]")
|
||||
|
||||
if (authquery.RowCount())
|
||||
return 0 // Authed
|
||||
else
|
||||
if (world_topic_spam_protect_ip == addr && abs(world_topic_spam_protect_time - world.time) < 50)
|
||||
|
||||
spawn(50)
|
||||
world_topic_spam_protect_time = world.time
|
||||
return "Bad Key (Throttled)"
|
||||
|
||||
world_topic_spam_protect_time = world.time
|
||||
world_topic_spam_protect_ip = addr
|
||||
|
||||
return "Bad Key"
|
||||
return 1 // Bad Key
|
||||
|
||||
@@ -397,3 +397,11 @@ STARLIGHT 0
|
||||
|
||||
## Uncomment to enable the antag contest!
|
||||
#ANTAG_CONTEST_ENABLED
|
||||
|
||||
## API Rate Limit in ds
|
||||
API_RATE_LIMIT 50
|
||||
|
||||
## API Rate Limit IP Whitelist
|
||||
## IPs that should not be throttled by the API RATE Limiter
|
||||
## IPs are separated by ;
|
||||
API_RATE_LIMIT_WHITELIST 127.0.0.1
|
||||
|
||||
36
html/changelogs/arrow768-apirewrite.yml
Normal file
36
html/changelogs/arrow768-apirewrite.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
################################
|
||||
# Example Changelog File
|
||||
#
|
||||
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
|
||||
#
|
||||
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
|
||||
# When it is, any changes listed below will disappear.
|
||||
#
|
||||
# Valid Prefixes:
|
||||
# bugfix
|
||||
# wip (For works in progress)
|
||||
# tweak
|
||||
# soundadd
|
||||
# sounddel
|
||||
# rscadd (general adding of nice things)
|
||||
# rscdel (general deleting of nice things)
|
||||
# imageadd
|
||||
# imagedel
|
||||
# maptweak
|
||||
# spellcheck (typo fixes)
|
||||
# experiment
|
||||
#################################
|
||||
|
||||
# Your name.
|
||||
author: Arrow768
|
||||
|
||||
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
|
||||
delete-after: True
|
||||
|
||||
# Any changes you've made. See valid prefix list above.
|
||||
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
|
||||
# SCREW THIS UP AND IT WON'T WORK.
|
||||
# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
|
||||
# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
|
||||
changes:
|
||||
- rscadd: "Rewrite of the API - This enables more advanced features in the webpanel"
|
||||
Reference in New Issue
Block a user