mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2025-12-30 20:13:08 +00:00
Swap memos to be Discord pins instead (#1382)
Intent: If using a Discord bot, make it so that the game is able to pull and process pinned messages in specific discord channels as memos.
This commit is contained in:
@@ -187,6 +187,7 @@ var/list/gamemode_cache = list()
|
||||
var/use_discord_bot = 0
|
||||
var/discord_bot_host = "localhost"
|
||||
var/discord_bot_port = 0
|
||||
var/use_discord_pins = 0
|
||||
var/python_path = "python" //Path to the python executable. Defaults to "python" on windows and "/usr/bin/env python2" on unix
|
||||
var/use_lib_nudge = 0 //Use the C library nudge instead of the python nudge.
|
||||
var/use_overmap = 0
|
||||
@@ -652,6 +653,9 @@ var/list/gamemode_cache = list()
|
||||
if("discord_bot_port")
|
||||
config.discord_bot_port = value
|
||||
|
||||
if("use_discord_pins")
|
||||
config.use_discord_pins = 1
|
||||
|
||||
if("python_path")
|
||||
if(value)
|
||||
config.python_path = value
|
||||
@@ -878,6 +882,8 @@ var/list/gamemode_cache = list()
|
||||
discord_bot.active = 1
|
||||
if ("robust_debug")
|
||||
discord_bot.robust_debug = 1
|
||||
if ("subscriber")
|
||||
discord_bot.subscriber_role = value
|
||||
else
|
||||
log_misc("Unknown setting in discord configuration: '[name]'")
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
#define CHAN_ADMIN "channel_admin"
|
||||
#define CHAN_CCIAA "channel_cciaa"
|
||||
#define CHAN_ANNOUNCE "channel_announce"
|
||||
#define CHAN_INVITE "channel_invite"
|
||||
|
||||
#define SEND_OK 0
|
||||
#define SEND_TIMEOUT 1
|
||||
#define ERROR_PROC 2
|
||||
#define ERROR_CURL 3
|
||||
#define ERROR_HTTP 4
|
||||
|
||||
var/datum/discord_bot/discord_bot = null
|
||||
|
||||
@@ -15,21 +22,34 @@ var/datum/discord_bot/discord_bot = null
|
||||
|
||||
discord_bot.update_channels()
|
||||
|
||||
if (config.use_discord_pins && server_greeting)
|
||||
server_greeting.update_pins()
|
||||
|
||||
return 1
|
||||
|
||||
/datum/discord_bot
|
||||
var/list/channels = list()
|
||||
var/list/channels_to_group = list() // Group flag -> list of channel datums map.
|
||||
var/list/channels = list() // Channel ID -> channel datum map. Will ensure that only one datum per channel ID exists.
|
||||
|
||||
var/datum/discord_channel/invite = null // The channel datum where the ingame Join Channel button will link to.
|
||||
|
||||
var/active = 0
|
||||
var/auth_token = ""
|
||||
var/subscriber_role = ""
|
||||
|
||||
var/robust_debug = 0
|
||||
|
||||
// Lazy man's rate limiting vars
|
||||
var/rate_limited_since = 0
|
||||
var/queue_being_pushed = 0
|
||||
var/datum/scheduled_task/push_task
|
||||
var/list/queue = list()
|
||||
|
||||
/*
|
||||
* Proc update_channels
|
||||
* Used to load channels from the database and construct them with the discord API.
|
||||
* Wipes all current channels and channel maps.
|
||||
*
|
||||
* @return num - Error code. 0 upon success.
|
||||
*/
|
||||
/datum/discord_bot/proc/update_channels()
|
||||
if (!active)
|
||||
return 1
|
||||
@@ -38,27 +58,56 @@ var/datum/discord_bot/discord_bot = null
|
||||
log_debug("BOREALIS: Failed to update channels due to missing database.")
|
||||
return 2
|
||||
|
||||
channels = list()
|
||||
// Reset the channel lists.
|
||||
channels_to_group.Cut()
|
||||
channels.Cut()
|
||||
|
||||
var/DBQuery/channel_query = dbcon.NewQuery("SELECT channel_group, channel_id FROM discord_channels")
|
||||
var/DBQuery/channel_query = dbcon.NewQuery("SELECT channel_group, channel_id, pin_flag, server_id FROM discord_channels")
|
||||
channel_query.Execute()
|
||||
|
||||
var/list/A
|
||||
while (channel_query.NextRow())
|
||||
if (isnull(channels[channel_query.item[1]]))
|
||||
channels[channel_query.item[1]] = list()
|
||||
// Create the channel map.
|
||||
if (isnull(channels_to_group[channel_query.item[1]]))
|
||||
channels_to_group[channel_query.item[1]] = list()
|
||||
|
||||
A = channels[channel_query.item[1]]
|
||||
A += channel_query.item[2]
|
||||
var/datum/discord_channel/B = channels[channel_query.item[2]]
|
||||
|
||||
// We don't have this channel datum yet.
|
||||
if (isnull(B))
|
||||
B = new(channel_query.item[2], channel_query.item[4], text2num(channel_query.item[3]))
|
||||
|
||||
if (!B)
|
||||
log_debug("BOREALIS: Bad channel data during update channels. [jointext(channel_query.item, ", ")].")
|
||||
continue
|
||||
|
||||
channels[channel_query.item[2]] = B
|
||||
|
||||
if (text2num(channel_query.item[3]))
|
||||
B.pin_flag |= text2num(channel_query.item[3])
|
||||
|
||||
// Add the channel to the required lists.
|
||||
channels_to_group[channel_query.item[1]] += B
|
||||
|
||||
if (!isnull(channels_to_group[CHAN_INVITE]))
|
||||
invite = channels_to_group[CHAN_INVITE][1]
|
||||
else if (robust_debug)
|
||||
log_debug("BOREALIS: No invite channel designated.")
|
||||
|
||||
log_debug("BOREALIS: Channels updated successfully.")
|
||||
return 0
|
||||
|
||||
/*
|
||||
* Proc send_message
|
||||
* Used to send a message to a specific channel group.
|
||||
*
|
||||
* @param text channel_group - The name of the channel group which to target.
|
||||
* @param text message - The message to send.
|
||||
*/
|
||||
/datum/discord_bot/proc/send_message(var/channel_group, var/message)
|
||||
if (!active || !auth_token)
|
||||
return
|
||||
|
||||
if (!channel_group || !channels.len || isnull(channels[channel_group]))
|
||||
if (!channel_group || !channels.len || isnull(channels_to_group[channel_group]))
|
||||
return
|
||||
|
||||
if (!message)
|
||||
@@ -70,18 +119,18 @@ var/datum/discord_bot/discord_bot = null
|
||||
// Let's run it through the proper JSON encoder, just in case of special characters.
|
||||
message = json_encode(list("content" = message))
|
||||
|
||||
var/list/A = channels[channel_group]
|
||||
var/list/A = channels_to_group[channel_group]
|
||||
var/list/sent = list()
|
||||
for (var/channel in A)
|
||||
if (send_post_request("https://discordapp.com/api/channels/[channel]/messages", message, "Authorization: Bot [auth_token]", "Content-Type: application/json") == 429)
|
||||
for (var/B in A)
|
||||
var/datum/discord_channel/channel = B
|
||||
if (channel.send_message_to(auth_token, message) == SEND_TIMEOUT)
|
||||
// Whoopsies, rate limited.
|
||||
// Set up the queue.
|
||||
rate_limited_since = world.time
|
||||
queue.Add(list(message, A - sent))
|
||||
queue.Add(list(list(message, A - sent)))
|
||||
|
||||
// Schedule a push.
|
||||
spawn (100)
|
||||
push_queue()
|
||||
if (!push_task)
|
||||
push_task = schedule_task_with_source_in(10 SECONDS, src, /datum/discord_bot/proc/push_queue)
|
||||
|
||||
// And exit.
|
||||
return
|
||||
@@ -89,43 +138,87 @@ var/datum/discord_bot/discord_bot = null
|
||||
sent += channel
|
||||
|
||||
if (robust_debug)
|
||||
log_debug("BOEALIS: Message sent to [channel_group]. JSON body: '[message]'")
|
||||
log_debug("BOREALIS: Message sent to [channel_group]. JSON body: '[message]'")
|
||||
|
||||
/*
|
||||
* Proc retreive_pins
|
||||
* Used to fetch a list of all pins from the designated channels.
|
||||
*
|
||||
* @return list - A multilayered list of flags associated with pins. Structure looks like this:
|
||||
* list("pin_flag" = list(list("author" = author name, "content" = content),
|
||||
* list("author" = author name, "content" = content)))
|
||||
*/
|
||||
/datum/discord_bot/proc/retreive_pins()
|
||||
if (!active || !auth_token)
|
||||
return list()
|
||||
|
||||
if (!channels.len || isnull(channels_to_group["channel_pins"]))
|
||||
testing("No group.")
|
||||
return list()
|
||||
|
||||
var/list/output = list()
|
||||
|
||||
for (var/A in channels_to_group["channel_pins"])
|
||||
var/datum/discord_channel/channel = A
|
||||
if (isnull(output["[channel.pin_flag]"]))
|
||||
output["[channel.pin_flag]"] = list()
|
||||
|
||||
output["[channel.pin_flag]"] += channel.get_pins(auth_token)
|
||||
|
||||
return output
|
||||
|
||||
/*
|
||||
* Proc retreive_invite
|
||||
* Used to retreive the invite to the invite channel.
|
||||
* One will be created if none exist.
|
||||
*
|
||||
* @return text - The invite URL to the designated invite channel.
|
||||
*/
|
||||
/datum/discord_bot/proc/retreive_invite()
|
||||
if (!active || !auth_token)
|
||||
return ""
|
||||
|
||||
if (!invite)
|
||||
return ""
|
||||
|
||||
var/res = invite.get_invite(auth_token)
|
||||
return isnum(res) ? "" : res
|
||||
|
||||
/*
|
||||
* Proc send_to_admin
|
||||
* Forwards a message to the admin channels.
|
||||
*/
|
||||
/datum/discord_bot/proc/send_to_admins(message)
|
||||
send_message(CHAN_ADMIN, message)
|
||||
|
||||
/*
|
||||
* Proc send_to_cciaa
|
||||
* Forwards a message to the CCIAA channels.
|
||||
*/
|
||||
/datum/discord_bot/proc/send_to_cciaa(message)
|
||||
send_message(CHAN_CCIAA, message)
|
||||
|
||||
/datum/discord_bot/proc/send_to_announce(message)
|
||||
/*
|
||||
* Proc send_to_announce
|
||||
* Forwards a message to the announcements channels.
|
||||
*/
|
||||
/datum/discord_bot/proc/send_to_announce(message, prepend_role = 0)
|
||||
if (prepend_role && subscriber_role)
|
||||
message = "<@&[subscriber_role]> " + message
|
||||
send_message(CHAN_ANNOUNCE, message)
|
||||
|
||||
/*
|
||||
* Proc push_queue
|
||||
* Handles the queue pushing for the bot. If there is no need to reschedule (all messages get successfully
|
||||
* pushed), then it deletes push_task and sets it back to null. Otherwise, it simply reschedules it.
|
||||
*/
|
||||
/datum/discord_bot/proc/push_queue()
|
||||
// What facking queue.
|
||||
if (!queue.len)
|
||||
if (!queue || !queue.len)
|
||||
if (robust_debug)
|
||||
log_debug("BOREALIS: Attempted to push a null length queue.")
|
||||
if (queue_being_pushed)
|
||||
queue_being_pushed = 0
|
||||
return
|
||||
|
||||
if (queue_being_pushed)
|
||||
if (robust_debug)
|
||||
log_debug("BOREALIS: Attempted to initialize a second queue driver.")
|
||||
return
|
||||
|
||||
if ((world.time - rate_limited_since) < 100)
|
||||
// Something broke the limit again. Ideally, this wouldn't happen. But sure.
|
||||
// Use a longer timeout, just in case.
|
||||
spawn (200)
|
||||
push_queue()
|
||||
|
||||
queue_being_pushed = 0
|
||||
return
|
||||
|
||||
// Async process lock var. No touchy.
|
||||
queue_being_pushed = 1
|
||||
|
||||
// A[1] - message body.
|
||||
// A[2] - list of channels to send to.
|
||||
var/message
|
||||
@@ -134,22 +227,216 @@ var/datum/discord_bot/discord_bot = null
|
||||
message = A[1]
|
||||
destinations = A[2]
|
||||
|
||||
for (var/channel in destinations)
|
||||
if (send_post_request("https://discordapp.com/api/channels/[channel]/messages", message, "Authorization: Bot [auth_token]", "Content-Type: application/json") == 429)
|
||||
// Limited again. Reschedule.
|
||||
rate_limited_since = world.time
|
||||
spawn (100)
|
||||
push_queue()
|
||||
for (var/B in destinations)
|
||||
var/datum/discord_channel/channel = B
|
||||
if (channel.send_message_to(auth_token, message) == SEND_TIMEOUT)
|
||||
// Tasks nuke themselves after use. So just make a new one! What could possibly go wrong!
|
||||
push_task = schedule_task_with_source_in(10 SECONDS, src, /datum/discord_bot/proc/push_queue)
|
||||
|
||||
queue_being_pushed = 0
|
||||
return
|
||||
else
|
||||
destinations.Remove(channel)
|
||||
|
||||
queue.Remove(A)
|
||||
|
||||
queue_being_pushed = 0
|
||||
// A holder class for channels.
|
||||
/datum/discord_channel
|
||||
var/id = ""
|
||||
var/server_id = ""
|
||||
var/pin_flag = 0
|
||||
var/invite_url = ""
|
||||
|
||||
/*
|
||||
* Constructor
|
||||
*
|
||||
* @param text _id - the discord API id of the channel, as a string.
|
||||
* @param text _sid - the discord API server id for the channel, as a string.
|
||||
* @param num _pin - the bitflags of admin permissions which have access to the pins from this channel.
|
||||
*/
|
||||
/datum/discord_channel/New(var/_id, var/_sid, var/_pin)
|
||||
id = _id
|
||||
server_id = _sid
|
||||
|
||||
if (_pin)
|
||||
pin_flag = _pin
|
||||
|
||||
/*
|
||||
* Proc send_message_to
|
||||
* Sends a message to the channel.
|
||||
*
|
||||
* @param text token - the authorization token to be used for the requests.
|
||||
* @param text message - the sanitized message content to be sent.
|
||||
* @return num - a specific return code for the discord_bot to handle.
|
||||
*/
|
||||
/datum/discord_channel/proc/send_message_to(var/token, var/message)
|
||||
if (!token || !message)
|
||||
return ERROR_PROC
|
||||
|
||||
var/res = send_post_request("https://discordapp.com/api/channels/[id]/messages", message, "Authorization: Bot [token]", "Content-Type: application/json")
|
||||
|
||||
switch (res)
|
||||
if (-1)
|
||||
return ERROR_PROC
|
||||
|
||||
if (200)
|
||||
return SEND_OK
|
||||
|
||||
if (429)
|
||||
return SEND_TIMEOUT
|
||||
|
||||
if (0 to 90)
|
||||
log_debug("BOREALIS: cURL error while forwarding message to Discord API: [res]. Message body: [message].")
|
||||
return ERROR_CURL
|
||||
|
||||
else
|
||||
log_debug("BOREALIS: HTTP error while forwarding message to Discord API: [res]. Channel: [id]. Message body: [message].")
|
||||
return ERROR_HTTP
|
||||
|
||||
/*
|
||||
* Proc get_pins
|
||||
* Retreives a list of pinned messages from the channel.
|
||||
*
|
||||
* @param text token - the authorization token to be used for the requests.
|
||||
* @return mixed - 2d array of content upon success, in format: list(list("author" = text, "content" = text)).
|
||||
* Num upon failure.
|
||||
*/
|
||||
/datum/discord_channel/proc/get_pins(var/token)
|
||||
var/res = send_get_request("https://discordapp.com/api/channels/[id]/pins", "Authorization: Bot [token]")
|
||||
|
||||
// Is a number, so an error code.
|
||||
if (isnum(res))
|
||||
switch (res)
|
||||
if (-1)
|
||||
return ERROR_PROC
|
||||
|
||||
if (0 to 90)
|
||||
log_debug("BOREALIS: cURL error while fetching pins from the Discord API: [res].")
|
||||
return ERROR_CURL
|
||||
|
||||
if (100 to 600)
|
||||
log_debug("BOREALIS: HTTP error while fetching pins from the Discord API: [res].")
|
||||
return ERROR_HTTP
|
||||
|
||||
else
|
||||
log_debug("BOREALIS: Unknown response code while fetching pins from the Discord API: [res].")
|
||||
return ERROR_PROC
|
||||
else
|
||||
var/list/A = res
|
||||
var/list/pinned_messages = list()
|
||||
|
||||
// Begin processing the list structure delivered back to us from send_get_request.
|
||||
for (var/list/B in A)
|
||||
var/content = B["content"]
|
||||
|
||||
// We have mentions to take care of.
|
||||
if (!isnull(B["mentions"]))
|
||||
var/list/mentions = B["mentions"]
|
||||
|
||||
for (var/list/C in mentions)
|
||||
content = replacetextEx(content, "<@[C["id"]]>", C["username"])
|
||||
|
||||
// Role mentions are up next.
|
||||
if (!isnull(B["mention_roles"]))
|
||||
var/list/mentions = B["mention_roles"]
|
||||
|
||||
for (var/C in mentions)
|
||||
content = replacetextEx(content, "<@&[C]>", "@SomeRole")
|
||||
|
||||
pinned_messages += list(list("author" = B["author"]["username"], "content" = content))
|
||||
|
||||
return pinned_messages
|
||||
|
||||
/*
|
||||
* Proc get_invite
|
||||
* Retreives (or creates if none found) an invite URL to the specific channel.
|
||||
*
|
||||
* @param text token - the authorization token to be used for the requests.
|
||||
* @return mixed - text upon success, the link to the invite; num upon failure.
|
||||
*/
|
||||
/datum/discord_channel/proc/get_invite(var/token)
|
||||
if (invite_url)
|
||||
return invite_url
|
||||
|
||||
var/res = send_get_request("https://discordapp.com/api/channels/[id]/invites", "Authorization: Bot [token]")
|
||||
|
||||
// Is a number, so an error code.
|
||||
if (isnum(res))
|
||||
switch (res)
|
||||
if (-1)
|
||||
return ERROR_PROC
|
||||
|
||||
if (0 to 90)
|
||||
log_debug("BOREALIS: cURL error while fetching pins from the Discord API: [res].")
|
||||
return ERROR_CURL
|
||||
|
||||
if (100 to 600)
|
||||
log_debug("BOREALIS: HTTP error while fetching pins from the Discord API: [res].")
|
||||
return ERROR_HTTP
|
||||
|
||||
else
|
||||
log_debug("BOREALIS: Unknown response code while fetching pins from the Discord API: [res].")
|
||||
return ERROR_PROC
|
||||
else
|
||||
var/list/A = res
|
||||
|
||||
// No length to return data, but a valid 200 return header.
|
||||
// So we simply have no invites active. Make one!
|
||||
if (!A || !A.len)
|
||||
return create_invite(token)
|
||||
|
||||
var/best_age = A[1]["max_age"]
|
||||
var/code = A[1]["code"]
|
||||
|
||||
// Find the best invite.
|
||||
// Why? Because round time expires are lame, I guess.
|
||||
if (best_age != 0)
|
||||
for (var/i = 2, i < A.len, i++)
|
||||
if (A[i]["max_age"] == 0)
|
||||
code = A[i]["code"]
|
||||
break
|
||||
|
||||
if (best_age < A[i]["max_age"])
|
||||
best_age = A[i]["max_age"]
|
||||
code = A[i]["code"]
|
||||
|
||||
// Sanity check for debug I guess.
|
||||
if (!code)
|
||||
log_debug("BOREALIS: Retreived an empty invite. This should not happen. Response object: [json_encode(A)]")
|
||||
|
||||
// Save the URL for later retreival.
|
||||
invite_url = "https://discord.gg/[code]"
|
||||
return invite_url
|
||||
|
||||
/*
|
||||
* Proc create_invite
|
||||
* Creates a permanent invite to a channel and returns it, under the assumption that
|
||||
* there are no other invites for this channel.
|
||||
*
|
||||
* @param text token - the authorization token to be used for the requests.
|
||||
* @return mixed - String upon success - the URL of the newly generated invite.
|
||||
* Num upon failure.
|
||||
*/
|
||||
/datum/discord_channel/proc/create_invite(var/token)
|
||||
var/data = list("max_age" = 0, "max_uses" = 0)
|
||||
var/res = send_post_request("https://discordapp.com/api/channels/[id]/invites", json_encode(data), "Authorization: Bot [token]", "Content-Type: application/json")
|
||||
|
||||
if (res == 200)
|
||||
var/list/get_req = send_get_request("https://discordapp.com/api/channels/[id]/invites", "Authorization: Bot [token]")
|
||||
|
||||
if (!istype(get_req) || !get_req.len)
|
||||
return ERROR_PROC
|
||||
|
||||
// The first index should now exist. So we just use that!
|
||||
invite_url = "https://discord.gg/[get_req[1]["code"]]"
|
||||
return invite_url
|
||||
|
||||
#undef CHAN_ADMIN
|
||||
#undef CHAN_CCIAA
|
||||
#undef CHAN_ANNOUNCE
|
||||
#undef CHAN_INVITE
|
||||
|
||||
#undef SEND_OK
|
||||
#undef SEND_TIMEOUT
|
||||
#undef ERROR_PROC
|
||||
#undef ERROR_CURL
|
||||
#undef ERROR_HTTP
|
||||
|
||||
@@ -46,8 +46,9 @@
|
||||
if (F["motd"])
|
||||
F["motd"] >> motd
|
||||
|
||||
if (F["memo"])
|
||||
F["memo"] >> memo_list
|
||||
if (!config.use_discord_pins)
|
||||
if (F["memo"])
|
||||
F["memo"] >> memo_list
|
||||
|
||||
update_data()
|
||||
|
||||
@@ -63,18 +64,32 @@
|
||||
motd = initial(motd)
|
||||
motd_hash = ""
|
||||
|
||||
if (memo_list.len)
|
||||
memo = ""
|
||||
for (var/ckey in memo_list)
|
||||
var/data = {"<p><b>[ckey]</b> wrote on [memo_list[ckey]["date"]]:<br>
|
||||
[memo_list[ckey]["content"]]</p>"}
|
||||
if (!config.use_discord_pins)
|
||||
// The initialization of memos in case use_discord_pins == 1 is done in discord_bot.dm
|
||||
// Primary reason is to avoid null references when the bot isn't created yet.
|
||||
if (memo_list.len)
|
||||
memo = ""
|
||||
for (var/ckey in memo_list)
|
||||
var/data = {"<p><b>[ckey]</b> wrote on [memo_list[ckey]["date"]]:<br>
|
||||
[memo_list[ckey]["content"]]</p>"}
|
||||
|
||||
memo += data
|
||||
memo += data
|
||||
|
||||
memo_hash = md5(memo)
|
||||
else
|
||||
memo = initial(memo)
|
||||
memo_hash = ""
|
||||
memo_hash = md5(memo)
|
||||
else
|
||||
memo = initial(memo)
|
||||
memo_hash = ""
|
||||
|
||||
/datum/server_greeting/proc/update_pins()
|
||||
var/list/temp_list = discord_bot.retreive_pins()
|
||||
|
||||
// A is a number in a string form
|
||||
// temp_list[A] is a list of lists.
|
||||
for (var/A in temp_list)
|
||||
var/list/memos = temp_list[A]
|
||||
var/flag = text2num(A)
|
||||
|
||||
memo_list += new /datum/memo_datum(memos, flag)
|
||||
|
||||
/*
|
||||
* Helper to update the MoTD or memo contents.
|
||||
@@ -94,9 +109,15 @@
|
||||
motd = new_value
|
||||
|
||||
if ("memo_write")
|
||||
if (config.use_discord_pins)
|
||||
return 0
|
||||
|
||||
memo_list[new_value[1]] = list("date" = time2text(world.realtime, "DD-MMM-YYYY"), "content" = new_value[2])
|
||||
|
||||
if ("memo_delete")
|
||||
if (config.use_discord_pins)
|
||||
return 0
|
||||
|
||||
if (memo_list[new_value])
|
||||
memo_list -= new_value
|
||||
else
|
||||
@@ -132,7 +153,7 @@
|
||||
if (motd_hash && user.prefs.motd_hash != motd_hash)
|
||||
outdated_info |= OUTDATED_MOTD
|
||||
|
||||
if (user.holder && memo_hash && user.prefs.memo_hash != memo_hash)
|
||||
if (user.holder && user.prefs.memo_hash != get_memo_hash(user))
|
||||
outdated_info |= OUTDATED_MEMO
|
||||
|
||||
if (user.prefs.notifications.len)
|
||||
@@ -185,13 +206,13 @@
|
||||
else
|
||||
if (outdated_info & OUTDATED_MEMO)
|
||||
data["update"] = 1
|
||||
data["changeHash"] = memo_hash
|
||||
data["changeHash"] = get_memo_hash(user)
|
||||
else
|
||||
data["update"] = 0
|
||||
data["changeHash"] = null
|
||||
|
||||
data["div"] = "#memo"
|
||||
data["content"] = memo
|
||||
data["content"] = get_memo_content(user)
|
||||
user << output(JS_SANITIZE(data), "greeting.browser:AddContent")
|
||||
|
||||
if (outdated_info & OUTDATED_MOTD)
|
||||
@@ -216,6 +237,81 @@
|
||||
if ("request_data")
|
||||
send_to_javascript(C)
|
||||
|
||||
/*
|
||||
* Gets the appropriate memo hash for the memo system in use.
|
||||
* Args:
|
||||
* - var/C client
|
||||
* Returns:
|
||||
* - string
|
||||
*/
|
||||
/datum/server_greeting/proc/get_memo_hash(var/client/C)
|
||||
if (!C || !C.holder)
|
||||
return ""
|
||||
|
||||
if (!config.use_discord_pins)
|
||||
return memo_hash
|
||||
|
||||
var/joint_checksum = ""
|
||||
for (var/A in memo_list)
|
||||
var/datum/memo_datum/memo = A
|
||||
if (C.holder.rights & memo.flag)
|
||||
joint_checksum += memo.hash
|
||||
|
||||
return md5(joint_checksum)
|
||||
|
||||
/*
|
||||
* Gets the appropriate memo content for the memo system in use.
|
||||
* Args:
|
||||
* - var/C client
|
||||
* Returns:
|
||||
* - string if old memo system is used (config.use_discord_pins = 0)
|
||||
* - list of strings if new memo system is used
|
||||
*/
|
||||
/datum/server_greeting/proc/get_memo_content(var/client/C)
|
||||
if (!C || !C.holder)
|
||||
return ""
|
||||
|
||||
if (!config.use_discord_pins)
|
||||
return memo
|
||||
|
||||
var/list/content = list()
|
||||
for (var/A in memo_list)
|
||||
var/datum/memo_datum/memo = A
|
||||
if (C.holder.rights & memo.flag)
|
||||
content += memo.contents
|
||||
|
||||
return content
|
||||
|
||||
/datum/memo_datum
|
||||
var/contents
|
||||
var/hash
|
||||
var/flag
|
||||
|
||||
/datum/memo_datum/New(var/list/input = list(), var/_flag)
|
||||
flag = _flag
|
||||
|
||||
// Yes. This is an unfortunately acceptable way of doing it.
|
||||
// Why? Because you cannot use numbers as indexes in an assoc list without fucking DM.
|
||||
var/static/list/flags_to_divs = list("[R_ADMIN]" = "danger",
|
||||
"[R_MOD]" = "warning",
|
||||
"[(R_MOD|R_ADMIN)]" = "warning",
|
||||
"[R_CCIAA]" = "info",
|
||||
"[R_DEV]" = "info")
|
||||
|
||||
if (input.len)
|
||||
contents = "<div class='alert alert-[flags_to_divs["[flag]"]]'>"
|
||||
for (var/i = 1, i <= input.len, i++)
|
||||
contents += "<b>[input[i]["author"]]</b> wrote:<br>[nl2br(input[i]["content"])]"
|
||||
|
||||
if (i < input.len)
|
||||
contents += "<hr></hr>"
|
||||
|
||||
contents += "</div>"
|
||||
else
|
||||
contents = ""
|
||||
|
||||
hash = md5(contents)
|
||||
|
||||
#undef OUTDATED_NOTE
|
||||
#undef OUTDATED_MEMO
|
||||
#undef OUTDATED_MOTD
|
||||
|
||||
@@ -283,7 +283,7 @@ var/global/list/additional_antag_types = list()
|
||||
/datum/game_mode/proc/declare_completion()
|
||||
|
||||
var/is_antag_mode = (antag_templates && antag_templates.len)
|
||||
var/discord_text = "@subscribers A round of **[name]** has ended! \[Game ID: [game_id]\]\n\n"
|
||||
var/discord_text = "A round of **[name]** has ended! \[Game ID: [game_id]\]\n\n"
|
||||
check_victory()
|
||||
if(is_antag_mode)
|
||||
sleep(10)
|
||||
@@ -298,7 +298,7 @@ var/global/list/additional_antag_types = list()
|
||||
sleep(10)
|
||||
print_ownerless_uplinks()
|
||||
|
||||
discord_bot.send_to_announce(discord_text)
|
||||
discord_bot.send_to_announce(discord_text, 1)
|
||||
discord_text = ""
|
||||
|
||||
var/clients = 0
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
var/result = call("ByondPOST.dll", "send_post_request")(arglist(args))
|
||||
|
||||
if (!result)
|
||||
log_debug("ByondPOST: No result returned from external library.")
|
||||
log_debug("ByondPOST POST: No result returned from external library.")
|
||||
return -1
|
||||
|
||||
var/list/A = params2list(result)
|
||||
@@ -67,14 +67,61 @@
|
||||
// Log the proc error. It should be reviewed by coders ASAP.
|
||||
switch (A["proc"])
|
||||
if ("1")
|
||||
log_debug("ByondPOST: Proc error: Too few arguments sent to function.")
|
||||
log_debug("ByondPOST POST: Proc error: Too few arguments sent to function.")
|
||||
if ("2")
|
||||
log_debug("ByondPOST: Proc error: Unable to initialize curl object.")
|
||||
log_debug("ByondPOST POST: Proc error: Unable to initialize curl object.")
|
||||
else
|
||||
log_debug("ByondPOST: Proc error: Unknown error.")
|
||||
log_debug("ByondPOST POST: Proc error: Unknown error.")
|
||||
return -1
|
||||
|
||||
// Curl oriented errors should leave the HTTP response code at 0, as no request was executed.
|
||||
// All HTTP oriented errors will definately return a response code other than 0, so prioritize that.
|
||||
// Fallback is a curl error code (0 - 92).
|
||||
return text2num(A["http"]) != 0 ? text2num(A["http"]) : text2num(A["curl"])
|
||||
|
||||
/*
|
||||
* A generic proc for sending a header equipped get requests with the aforementioned .DLL files.
|
||||
* If you're using this without sending custom headers, please stop. Use world.Export() instead.
|
||||
* Expected arg structure:
|
||||
* 1st arg - the url
|
||||
* 2nd - nth arg - individual headers and their values in format: "headername: value"
|
||||
*
|
||||
* @return mixed - Returns list if request was successful, integer (specific cURL or HTTP error) if failed.
|
||||
* -1 indicates proc or library failure.
|
||||
* 0 - 92 are curl errors, and are usually accompanied by a HTTP response code of 0 (request was never made).
|
||||
* 100 - 6xx are HTTP response codes. Curl error code should be 0 in this case, but, in case that it is not,
|
||||
* the HTTP response code is always returned as long as it is not 0.
|
||||
*
|
||||
*/
|
||||
/proc/send_get_request()
|
||||
if (args.len < 2)
|
||||
return -1
|
||||
|
||||
var/result = call("ByondPOST.dll", "send_get_request")(arglist(args))
|
||||
|
||||
if (!result)
|
||||
log_debug("ByondPOST GET: No result returned from external library.")
|
||||
return -1
|
||||
|
||||
var/list/A
|
||||
|
||||
// Try to evaluate it as JSON data (successful request)
|
||||
try
|
||||
A = json_decode(result)
|
||||
|
||||
return A
|
||||
catch()
|
||||
// Nope, we failed. do regular error parsing instead.
|
||||
A = params2list(result)
|
||||
|
||||
if (!isnull(A["proc"]))
|
||||
switch (A["proc"])
|
||||
if ("1")
|
||||
log_debug("ByondPOST GET: Proc error: Too few arguments sent to function.")
|
||||
if ("2")
|
||||
log_debug("ByondPOST GET: Proc error: Unable to initialize curl object.")
|
||||
else
|
||||
log_debug("ByondPOST GET: Proc error: Unknown error.")
|
||||
return -1
|
||||
|
||||
return text2num(A["http"]) != 0 ? text2num(A["http"]) : text2num(A["curl"])
|
||||
|
||||
Reference in New Issue
Block a user