diff --git a/SQL/database_changelog.txt b/SQL/database_changelog.txt
index c204cbaaf92..27d6cd3190c 100644
--- a/SQL/database_changelog.txt
+++ b/SQL/database_changelog.txt
@@ -1,15 +1,42 @@
Any time you make a change to the schema files, remember to increment the database schema version. Generally increment the minor number, major should be reserved for significant changes to the schema. Both values go up to 255.
-The latest database version is 5.9; The query to update the schema revision table is:
+The latest database version is 5.10; The query to update the schema revision table is:
-INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 9);
+INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 10);
or
-INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 9);
+INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 10);
In any query remember to add a prefix to the table names if you use one.
-----------------------------------------------------
+Version 5.10, 7 August 2020, by oranges
+
+Changes how the discord verification process works.
+Adds the discord_links table, and migrates discord id entries from player table to the discord links table in a once off operation and then removes the discord id
+on the player table
+
+START TRANSACTION;
+
+DROP TABLE IF EXISTS `discord_links`;
+CREATE TABLE `discord_links` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `ckey` VARCHAR(32) NOT NULL,
+ `discord_id` BIGINT(20) DEFAULT NULL,
+ `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `one_time_token` VARCHAR(100) NOT NULL,
+ `valid` BOOLEAN NOT NULL DEFAULT FALSE,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
+
+INSERT INTO `discord_links` (`ckey`, `discord_id`, `one_time_token`, `valid`) SELECT `ckey`, `discord_id`, CONCAT("presync_from_player_table_", `ckey`), TRUE FROM `player` WHERE discord_id IS NOT NULL;
+
+ALTER TABLE `player` DROP COLUMN `discord_id`;
+
+COMMIT;
+
+-----------------------------------------------------
+
Version 5.9, 19 April 2020, by Jordie0608
Updates and improvements to poll handling.
Added the `deleted` column to tables 'poll_option', 'poll_textreply' and 'poll_vote' and the columns `created_datetime`, `subtitle`, `allow_revoting` and `deleted` to 'poll_question'.
diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql
index 176475a40c9..a01787e395b 100644
--- a/SQL/tgstation_schema.sql
+++ b/SQL/tgstation_schema.sql
@@ -322,7 +322,6 @@ CREATE TABLE `player` (
`lastadminrank` varchar(32) NOT NULL DEFAULT 'Player',
`accountjoindate` DATE DEFAULT NULL,
`flags` smallint(5) unsigned DEFAULT '0' NOT NULL,
- `discord_id` BIGINT(20) NULL DEFAULT NULL,
PRIMARY KEY (`ckey`),
KEY `idx_player_cid_ckey` (`computerid`,`ckey`),
KEY `idx_player_ip_ckey` (`ip`,`ckey`)
@@ -574,6 +573,20 @@ END
$$
DELIMITER ;
+--
+-- Table structure for table `discord_links`
+--
+DROP TABLE IF EXISTS `discord_links`;
+CREATE TABLE `discord_links` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `ckey` VARCHAR(32) NOT NULL,
+ `discord_id` BIGINT(20) DEFAULT NULL,
+ `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `one_time_token` VARCHAR(100) NOT NULL,
+ `valid` BOOLEAN NOT NULL DEFAULT FALSE,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
+
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql
index cf4818c30c9..03521308c3f 100644
--- a/SQL/tgstation_schema_prefixed.sql
+++ b/SQL/tgstation_schema_prefixed.sql
@@ -322,7 +322,6 @@ CREATE TABLE `SS13_player` (
`lastadminrank` varchar(32) NOT NULL DEFAULT 'Player',
`accountjoindate` DATE DEFAULT NULL,
`flags` smallint(5) unsigned DEFAULT '0' NOT NULL,
- `discord_id` BIGINT(20) NULL DEFAULT NULL,
PRIMARY KEY (`ckey`),
KEY `idx_player_cid_ckey` (`computerid`,`ckey`),
KEY `idx_player_ip_ckey` (`ip`,`ckey`)
@@ -574,6 +573,20 @@ END
$$
DELIMITER ;
+--
+-- Table structure for table `discord_links`
+--
+DROP TABLE IF EXISTS `SS13_discord_links`;
+CREATE TABLE `SS13_discord_links` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `ckey` VARCHAR(32) NOT NULL,
+ `discord_id` BIGINT(20) DEFAULT NULL,
+ `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `one_time_token` VARCHAR(100) NOT NULL,
+ `valid` BOOLEAN NOT NULL DEFAULT FALSE,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
+
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 975a7011753..76ff74dda84 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -20,7 +20,7 @@
*
* make sure you add an update to the schema_version stable in the db changelog
*/
-#define DB_MINOR_VERSION 9
+#define DB_MINOR_VERSION 10
//! ## Timing subsystem
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 6b20b969624..25991c103f1 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -295,6 +295,21 @@
return copytext(text, 1, i + 1)
return ""
+/**
+ * Truncate a string to the given length
+ *
+ * Will only truncate if the string is larger than the length and *ignores unicode concerns*
+ *
+ * This exists soley because trim does other stuff too.
+ *
+ * Arguments:
+ * * text - String
+ * * max_length - integer length to truncate at
+ */
+/proc/truncate(text, max_length)
+ if(length(text) > max_length)
+ return copytext(text, 1, max_length)
+
//Returns a string with reserved characters and spaces before the first word and after the last word removed.
/proc/trim(text, max_length)
if(max_length)
diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm
index df0a49a3cb7..927b169ae03 100644
--- a/code/_globalvars/logging.dm
+++ b/code/_globalvars/logging.dm
@@ -44,8 +44,6 @@ GLOBAL_VAR(tgui_log)
GLOBAL_PROTECT(tgui_log)
GLOBAL_VAR(world_shuttle_log)
GLOBAL_PROTECT(world_shuttle_log)
-GLOBAL_VAR(discord_api_log)
-GLOBAL_PROTECT(discord_api_log)
GLOBAL_VAR(demo_log)
GLOBAL_PROTECT(demo_log)
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 24e282086a5..bd3d5ae5010 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -495,15 +495,3 @@
/datum/config_entry/string/centcom_source_whitelist
-// DISCORD ROLE STUFFS
-// Using strings for everything because BYOND does not like numbers this big
-// (exception to the above is required living hours haha)
-/datum/config_entry/flag/enable_discord_autorole
-
-/datum/config_entry/number/required_living_hours
-
-/datum/config_entry/string/discord_token
-
-/datum/config_entry/string/discord_guildid
-
-/datum/config_entry/string/discord_roleid
diff --git a/code/controllers/subsystem/discord.dm b/code/controllers/subsystem/discord.dm
index f53c983acfc..0ed4c450b43 100644
--- a/code/controllers/subsystem/discord.dm
+++ b/code/controllers/subsystem/discord.dm
@@ -1,30 +1,33 @@
-/*
-NOTES:
-There is a DB table to track ckeys and associated discord IDs.
-This system REQUIRES TGS, and will auto-disable if TGS is not present.
-The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasn't properly shutdown
-It only writes to the disk every 5 minutes, and it won't write to disk if the file is the same as it was the last time it was written. This is to save on disk writes
-The system is kept per-server (EG: Terry will not notify people who pressed notify on Sybil), but the accounts are between servers so you dont have to relink on each server.
-
-##################
-# HOW THIS WORKS #
-##################
-
-ROUNDSTART:
-1] The file is loaded and the discord IDs are extracted
-2] A ping is sent to the discord with the IDs of people who wished to be notified
-3] The file is emptied
-
-MIDROUND:
-1] Someone usees the notify verb, it adds their discord ID to the list.
-2] On fire, it will write that to the disk, as long as conditions above are correct
-
-END ROUND:
-1] The file is force-saved, incase it hasn't fired at end round
-
-This is an absolute clusterfuck, but its my clusterfuck -aa07
-*/
-
+/**
+ * # Discord Subsystem
+ *
+ * This subsystem handles some integrations with discord
+ *
+ *
+ * NOTES:
+ * * There is a DB table to track ckeys and associated discord IDs. (discord_link)
+ * * This system REQUIRES TGS for notifying users at end of the round
+ * * The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasn't properly shutdown
+ * * It only writes to the disk every 5 minutes, and it won't write to disk if the file is the same as it was the last time it was written. This is to save on disk writes
+ * * The system is kept per-server (EG: Terry will not notify people who pressed notify on Sybil), but the accounts are between servers so you dont have to relink on each server.
+ *
+ *
+ * ## HOW NOTIFYING WORKS
+ *
+ * ### ROUNDSTART:
+ * 1) The file is loaded and the discord IDs are extracted
+ * 2) A ping is sent to the discord with the IDs of people who wished to be notified
+ * 3) The file is emptied
+ *
+ * ### MIDROUND:
+ * 1) Someone usees the notify verb, it adds their discord ID to the list.
+ * 2) On fire, it will write that to the disk, as long as conditions above are correct
+ *
+ * ### END ROUND:
+ * 1) The file is force-saved, incase it hasn't fired at end round
+ *
+ * This is an absolute clusterfuck, but its my clusterfuck -aa07
+ */
SUBSYSTEM_DEF(discord)
name = "Discord"
wait = 3000
@@ -36,15 +39,22 @@ SUBSYSTEM_DEF(discord)
var/list/notify_members_cache = list()
/// People to notify on roundstart
var/list/people_to_notify = list()
- /// List that holds accounts to link, used in conjunction with TGS
- var/list/account_link_cache = list()
- /// list of people who tried to reverify, so they can only do it once per round as a shitty slowdown
- var/list/reverify_cache = list()
+
+ /// People who have tried to verify this round already
+ var/list/reverify_cache
+
+ /// Common words list, used to generate one time tokens
+ var/list/common_words
+
+ /// The file where notification status is saved
var/notify_file = file("data/notify.json")
+
/// Is TGS enabled (If not we won't fire because otherwise this is useless)
var/enabled = FALSE
/datum/controller/subsystem/discord/Initialize(start_timeofday)
+ common_words = world.file2list("strings/1000_most_common.txt")
+ reverify_cache = list()
// Check for if we are using TGS, otherwise return and disables firing
if(world.TgsAvailable())
enabled = TRUE // Allows other procs to use this (Account linking, etc)
@@ -81,70 +91,186 @@ SUBSYSTEM_DEF(discord)
WRITE_FILE(notify_file, json_encode(notify_members)) // Writes the file
notify_members_cache = notify_members // Updates the cache list
-// Returns ID from ckey
+/**
+ * Given a ckey, look up the discord user id attached to the user, if any
+ *
+ * This gets the most recent entry from the discord link table that is associated with the given ckey
+ *
+ * Arguments:
+ * * lookup_ckey A string representing the ckey to search on
+ */
/datum/controller/subsystem/discord/proc/lookup_id(lookup_ckey)
- //We cast the discord ID to varchar to prevent BYOND mangling
- //it into it's scientific notation
- var/datum/db_query/query_get_discord_id = SSdbcore.NewQuery(
- "SELECT CAST(discord_id AS CHAR(25)) FROM [format_table_name("player")] WHERE ckey = :ckey",
- list("ckey" = lookup_ckey)
- )
- if(!query_get_discord_id.Execute())
- qdel(query_get_discord_id)
- return
- if(query_get_discord_id.NextRow())
- . = query_get_discord_id.item[1]
- qdel(query_get_discord_id)
+ var/datum/discord_link_record/link = find_discord_link_by_ckey(lookup_ckey)
+ if(link)
+ return link.discord_id
-// Returns ckey from ID
+/**
+ * Given a discord id as a string, look up the ckey attached to that account, if any
+ *
+ * This gets the most recent entry from the discord_link table that is associated with this discord id snowflake
+ *
+ * Arguments:
+ * * lookup_id The discord id as a string
+ */
/datum/controller/subsystem/discord/proc/lookup_ckey(lookup_id)
- var/datum/db_query/query_get_discord_ckey = SSdbcore.NewQuery(
- "SELECT ckey FROM [format_table_name("player")] WHERE discord_id = :discord_id",
- list("discord_id" = lookup_id)
+ var/datum/discord_link_record/link = find_discord_link_by_discord_id(lookup_id)
+ if(link)
+ return link.ckey
+
+/datum/controller/subsystem/discord/proc/get_or_generate_one_time_token_for_ckey(ckey)
+ // Is there an existing valid one time token
+ var/datum/discord_link_record/link = find_discord_link_by_ckey(ckey, timebound = TRUE)
+ if(link)
+ return link.one_time_token
+
+ // Otherwise we make one
+ return generate_one_time_token(ckey)
+
+/**
+ * Generate a timebound token for discord verification
+ *
+ * This uses the common word list to generate a six word random token, this token can then be fed to a discord bot that has access
+ * to the same database, and it can use it to link a ckey to a discord id, with minimal user effort
+ *
+ * It returns the token to the calling proc, after inserting an entry into the discord_link table of the following form
+ *
+ * ```
+ * (unique_id, ckey, null, the current time, the one time token generated)
+ * the null value will be filled out with the discord id by the integrated discord bot when a user verifies
+ * ```
+ *
+ * Notes:
+ * * The token is guaranteed to unique during it's validity period
+ * * The validity period is currently set at 4 hours
+ * * a token may not be unique outside it's validity window (to reduce conflicts)
+ *
+ * Arguments:
+ * * ckey_for a string representing the ckey this token is for
+ *
+ * Returns a string representing the one time token
+ */
+/datum/controller/subsystem/discord/proc/generate_one_time_token(ckey_for)
+
+ var/not_unique = TRUE
+ var/one_time_token = ""
+ // While there's a collision in the token, generate a new one (should rarely happen)
+ while(not_unique)
+ //Column is varchar 100, so we trim just in case someone does us the dirty later
+ one_time_token = trim("[pick(common_words)]-[pick(common_words)]-[pick(common_words)]-[pick(common_words)]-[pick(common_words)]-[pick(common_words)]", 100)
+
+ not_unique = find_discord_link_by_token(one_time_token, timebound = TRUE)
+
+ // Insert into the table, null in the discord id, id and timestamp and valid fields so the db fills them out where needed
+ var/datum/db_query/query_insert_link_record = SSdbcore.NewQuery(
+ "INSERT INTO [format_table_name("discord_links")] (ckey, one_time_token) VALUES(:ckey, :token)",
+ list("ckey" = ckey_for, "token" = one_time_token)
)
- if(!query_get_discord_ckey.Execute())
- qdel(query_get_discord_ckey)
+
+ if(!query_insert_link_record.Execute())
+ qdel(query_insert_link_record)
+ return ""
+
+ //Cleanup
+ qdel(query_insert_link_record)
+ return one_time_token
+
+/**
+ * Find discord link entry by the passed in user token
+ *
+ * This will look into the discord link table and return the *first* entry that matches the given one time token
+ *
+ * Remember, multiple entries can exist, as they are only guaranteed to be unique for their validity period
+ *
+ * Arguments:
+ * * one_time_token the string of words representing the one time token
+ * * timebound A boolean flag, that specifies if it should only look for entries within the last 4 hours, off by default
+ *
+ * Returns a [/datum/discord_link_record]
+ */
+/datum/controller/subsystem/discord/proc/find_discord_link_by_token(one_time_token, timebound = FALSE)
+ var/timeboundsql = ""
+ if(timebound)
+ timeboundsql = "AND timestamp >= Now() - INTERVAL 4 HOUR"
+ var/query = "SELECT CAST(discord_id AS CHAR(25)), ckey, MAX(timestamp), one_time_token FROM [format_table_name("discord_links")] WHERE one_time_token = :one_time_token [timeboundsql] GROUP BY ckey, discord_id, one_time_token LIMIT 1"
+ var/datum/db_query/query_get_discord_link_record = SSdbcore.NewQuery(
+ query,
+ list("one_time_token" = one_time_token)
+ )
+ if(!query_get_discord_link_record.Execute())
+ qdel(query_get_discord_link_record)
return
- if(query_get_discord_ckey.NextRow())
- . = query_get_discord_ckey.item[1]
- qdel(query_get_discord_ckey)
+ if(query_get_discord_link_record.NextRow())
+ var/result = query_get_discord_link_record.item
+ . = new /datum/discord_link_record(result[2], result[1], result[4], result[3])
-// Finalises link
-/datum/controller/subsystem/discord/proc/link_account(ckey)
- var/datum/db_query/link_account = SSdbcore.NewQuery(
- "UPDATE [format_table_name("player")] SET discord_id = :discord_id WHERE ckey = :ckey",
- list("discord_id" = account_link_cache[ckey], "ckey" = ckey)
- )
- link_account.Execute()
- qdel(link_account)
- account_link_cache -= ckey
+ //Make sure we clean up the query
+ qdel(query_get_discord_link_record)
-// Unlink account (Admin verb used)
-/datum/controller/subsystem/discord/proc/unlink_account(ckey)
- var/datum/db_query/unlink_account = SSdbcore.NewQuery(
- "UPDATE [format_table_name("player")] SET discord_id = NULL WHERE ckey = :ckey",
+/**
+ * Find discord link entry by the passed in user ckey
+ *
+ * This will look into the discord link table and return the *first* entry that matches the given ckey
+ *
+ * Remember, multiple entries can exist
+ *
+ * Arguments:
+ * * ckey the users ckey as a string
+ * * timebound should we search only in the last 4 hours
+ *
+ * Returns a [/datum/discord_link_record]
+ */
+/datum/controller/subsystem/discord/proc/find_discord_link_by_ckey(ckey, timebound = FALSE)
+ var/timeboundsql = ""
+ if(timebound)
+ timeboundsql = "AND timestamp >= Now() - INTERVAL 4 HOUR"
+
+ var/query = "SELECT CAST(discord_id AS CHAR(25)), ckey, MAX(timestamp), one_time_token FROM [format_table_name("discord_links")] WHERE ckey = :ckey [timeboundsql] GROUP BY ckey, discord_id, one_time_token LIMIT 1"
+ var/datum/db_query/query_get_discord_link_record = SSdbcore.NewQuery(
+ query,
list("ckey" = ckey)
)
- unlink_account.Execute()
- qdel(unlink_account)
-
-// Clean up a discord account mention
-/datum/controller/subsystem/discord/proc/id_clean(input)
- var/regex/num_only = regex("\[^0-9\]", "g")
- return num_only.Replace(input, "")
-
-/datum/controller/subsystem/discord/proc/grant_role(id)
- // Ignore this shit if config isn't enabled for it
- if(!CONFIG_GET(flag/enable_discord_autorole))
+ if(!query_get_discord_link_record.Execute())
+ qdel(query_get_discord_link_record)
return
- var/url = "https://discord.com/api/guilds/[CONFIG_GET(string/discord_guildid)]/members/[id]/roles/[CONFIG_GET(string/discord_roleid)]"
+ if(query_get_discord_link_record.NextRow())
+ var/result = query_get_discord_link_record.item
+ . = new /datum/discord_link_record(result[2], result[1], result[4], result[3])
- // Make the request
- var/datum/http_request/req = new()
- req.prepare(RUSTG_HTTP_METHOD_PUT, url, "", list("Authorization" = "Bot [CONFIG_GET(string/discord_token)]"))
- req.begin_async()
- UNTIL(req.is_complete())
- var/datum/http_response/res = req.into_response()
+ //Make sure we clean up the query
+ qdel(query_get_discord_link_record)
- WRITE_LOG(GLOB.discord_api_log, "PUT [url] returned [res.status_code] [res.body]")
+
+/**
+ * Find discord link entry by the passed in user ckey
+ *
+ * This will look into the discord link table and return the *first* entry that matches the given ckey
+ *
+ * Remember, multiple entries can exist
+ *
+ * Arguments:
+ * * discord_id The users discord id (string)
+ * * timebound should we search only in the last 4 hours
+ *
+ * Returns a [/datum/discord_link_record]
+ */
+/datum/controller/subsystem/discord/proc/find_discord_link_by_discord_id(discord_id, timebound = FALSE)
+ var/timeboundsql = ""
+ if(timebound)
+ timeboundsql = "AND timestamp >= Now() - INTERVAL 4 HOUR"
+
+ var/query = "SELECT CAST(discord_id AS CHAR(25)), ckey, MAX(timestamp), one_time_token FROM [format_table_name("discord_links")] WHERE discord_id = :discord_id [timeboundsql] GROUP BY ckey, discord_id, one_time_token LIMIT 1"
+ var/datum/db_query/query_get_discord_link_record = SSdbcore.NewQuery(
+ query,
+ list("discord_id" = discord_id)
+ )
+ if(!query_get_discord_link_record.Execute())
+ qdel(query_get_discord_link_record)
+ return
+
+ if(query_get_discord_link_record.NextRow())
+ var/result = query_get_discord_link_record.item
+ . = new /datum/discord_link_record(result[2], result[1], result[4], result[3])
+
+ //Make sure we clean up the query
+ qdel(query_get_discord_link_record)
diff --git a/code/game/world.dm b/code/game/world.dm
index 4ed77a3ad44..25d0cac8dd1 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -144,7 +144,6 @@ GLOBAL_VAR(restart_counter)
GLOB.world_paper_log = "[GLOB.log_directory]/paper.log"
GLOB.tgui_log = "[GLOB.log_directory]/tgui.log"
GLOB.world_shuttle_log = "[GLOB.log_directory]/shuttle.log"
- GLOB.discord_api_log = "[GLOB.log_directory]/discord_api_log.log"
GLOB.demo_log = "[GLOB.log_directory]/demo.log"
@@ -164,7 +163,6 @@ GLOBAL_VAR(restart_counter)
start_log(GLOB.world_job_debug_log)
start_log(GLOB.tgui_log)
start_log(GLOB.world_shuttle_log)
- start_log(GLOB.discord_api_log)
GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently
if(fexists(GLOB.config_error_log))
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 514b06977b1..660b0374365 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -75,7 +75,6 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/resetasaycolor,
/client/proc/toggleadminhelpsound,
/client/proc/respawn_character,
- /client/proc/discord_id_manipulation,
/datum/admins/proc/open_borgopanel
)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel))
diff --git a/code/modules/discord/accountlink.dm b/code/modules/discord/accountlink.dm
index 00b6d2df054..d19e6959c59 100644
--- a/code/modules/discord/accountlink.dm
+++ b/code/modules/discord/accountlink.dm
@@ -1,93 +1,32 @@
-// Verb to link discord accounts to BYOND accounts
-/client/verb/linkdiscord()
- set category = "OOC"
- set name = "Link Discord Account"
- set desc = "Link your discord account to your BYOND account."
-
- // Safety checks
- if(!CONFIG_GET(flag/sql_enabled))
- to_chat(src, "This feature requires the SQL backend to be running.")
- return
-
- if(!SSdiscord) // SS is still starting
- to_chat(src, "The server is still starting up. Please wait before attempting to link your account!")
- return
-
- if(!SSdiscord.enabled)
- to_chat(src, "This feature requires the server is running on the TGS toolkit.")
- return
-
- var/stored_id = SSdiscord.lookup_id(usr.ckey)
- if(!stored_id) // Account is not linked
- var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No","Cancel Linking")
- if(know_how == "No") // Opens discord support on how to collect IDs
- src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID")
- if(know_how == "Cancel Linking")
- return
- var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null
- SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces
- alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])")
-
- else // Account is already linked
- var/choice = alert("You already have the Discord Account [stored_id] linked to [usr.ckey]. Would you like to link a different account?","Already Linked","Yes","No")
- if(choice == "Yes")
- var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No", "Cancel Linking")
- if(know_how == "No")
- src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID")
-
- if(know_how == "Cancel Linking")
- return
-
- var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null
- SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces
- alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])")
- // This is so people cant fill the notify list with a fuckload of ckeys
- SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer
-
// IF you have linked your account, this will trigger a verify of the user
/client/verb/verify_in_discord()
set category = "OOC"
set name = "Verify Discord Account"
- set desc = "Verify or reverify your discord account against your linked ckey"
+ set desc = "Verify your discord account with your BYOND account"
// Safety checks
if(!CONFIG_GET(flag/sql_enabled))
to_chat(src, "This feature requires the SQL backend to be running.")
return
-
- // ss is still starting
- if(!SSdiscord)
- to_chat(src, "The server is still starting up. Please wait before attempting to link your account!")
+ if(!SSdiscord && !SSdiscord.reverify_cache)
+ to_chat(src, "Wait for the Discord subsystem to finish initialising")
return
+ var/message = ""
+ // Simple sanity check to prevent a user doing this too often
+ var/cached_one_time_token = SSdiscord.reverify_cache[usr.ckey]
+ if(cached_one_time_token && cached_one_time_token != "")
+ message = "You already generated your one time token, it is [cached_one_time_token], if you need a new one, you will have to wait until the round ends, or switch to another server, try verifying yourself in discord by using the command \".verify [cached_one_time_token]\""
- // check that tgs is alive and well
- if(!SSdiscord.enabled)
- to_chat(src, "This feature requires the server is running on the TGS toolkit.")
- return
- // check that this is not an IDIOT mistaking us for an attack vector
- if(SSdiscord.reverify_cache[usr.ckey] == TRUE)
- to_chat(src, "Thou can only do this once a round, if you're stuck seek help.")
- return
- SSdiscord.reverify_cache[usr.ckey] = TRUE
+ else
+ // Will generate one if an expired one doesn't exist already, otherwise will grab existing token
+ var/one_time_token = SSdiscord.get_or_generate_one_time_token_for_ckey(ckey)
+ SSdiscord.reverify_cache[usr.ckey] = one_time_token
+ message = "Your one time token is: [one_time_token], Assuming you have the required living minutes in game, you can now verify yourself in discord by using the command \".verify [one_time_token]\""
- // check that account is linked with discord
- var/stored_id = SSdiscord.lookup_id(usr.ckey)
- if(!stored_id) // Account is not linked
- to_chat(usr, "Link your discord account via the linkdiscord verb in the OOC tab first");
- return
+ //Now give them a browse window so they can't miss whatever we told them
+ var/datum/browser/window = new/datum/browser(usr, "discordverification", "Discord verification")
+ window.set_content("[message]")
+ window.open()
- // check for living hours requirement
- var/required_living_minutes = CONFIG_GET(number/required_living_hours) * 60
- var/living_minutes = usr.client ? usr.client.get_exp_living(TRUE) : 0
- if(required_living_minutes <= 0)
- CRASH("The discord verification system is setup to require zero hours or less, this is likely a configuration bug")
-
- if(living_minutes < required_living_minutes)
- to_chat(usr, "You must have at least [required_living_minutes] minutes of living " \
- + "playtime in a round to verify. You have [living_minutes] minutes. Play more!")
- return
- // honey its time for your role flattening
- to_chat(usr, "Discord verified")
- SSdiscord.grant_role(stored_id)
diff --git a/code/modules/discord/discord_link_record.dm b/code/modules/discord/discord_link_record.dm
new file mode 100644
index 00000000000..d27bde8e3c7
--- /dev/null
+++ b/code/modules/discord/discord_link_record.dm
@@ -0,0 +1,24 @@
+/// Represents a record from the discord link table in a nicer format
+/datum/discord_link_record
+ var/ckey
+ var/discord_id
+ var/one_time_token
+ var/timestamp
+
+/**
+ * Generate a discord link datum from the values
+ *
+ * This is only used by SSdiscord wrapper functions for now, so you can reference the fields
+ * slightly easier
+ *
+ * Arguments:
+ * * ckey Ckey as a string
+ * * discord_id Discord id as a string
+ * * one_time_token as a string
+ * * timestamp as a string
+ */
+/datum/discord_link_record/New(ckey, discord_id, one_time_token, timestamp)
+ src.ckey = ckey
+ src.discord_id = discord_id
+ src.one_time_token = one_time_token
+ src.timestamp = timestamp
diff --git a/code/modules/discord/manipulation.dm b/code/modules/discord/manipulation.dm
deleted file mode 100644
index d89953c6bbd..00000000000
--- a/code/modules/discord/manipulation.dm
+++ /dev/null
@@ -1,36 +0,0 @@
-// Verb to manipulate IDs and ckeys
-/client/proc/discord_id_manipulation()
- set name = "Discord Manipulation"
- set category = "Admin"
-
- if(!check_rights(R_ADMIN))
- return
-
- holder.discord_manipulation()
-
-
-/datum/admins/proc/discord_manipulation()
- if(!usr.client.holder)
- return
-
- if(!SSdiscord.enabled)
- to_chat(usr, "TGS is not enabled")
- return
-
- var/lookup_choice = alert(usr, "Do you wish to lookup account by ID or ckey?", "Lookup Type", "ID", "Ckey", "Cancel")
- switch(lookup_choice)
- if("ID")
- var/lookup_id = input(usr,"Enter Discord ID to lookup ckey") as text|null
- var/returned_ckey = SSdiscord.lookup_ckey(lookup_id)
- if(returned_ckey)
- var/unlink_choice = alert(usr, "Discord ID [lookup_id] is linked to Ckey [returned_ckey]. Do you wish to unlink or cancel?", "Account Found", "Unlink", "Cancel")
- if(unlink_choice == "Unlink")
- SSdiscord.unlink_account(returned_ckey)
- else
- to_chat(usr, "Discord ID [lookup_id] has no associated ckey")
- if("Ckey")
- var/lookup_ckey = input(usr,"Enter Ckey to lookup ID") as text|null
- var/returned_id = SSdiscord.lookup_id(lookup_ckey)
- if(returned_id)
- to_chat(usr, "Ckey [lookup_ckey] is assigned to Discord ID [returned_id]")
- to_chat(usr, "Discord mention format: <@[returned_id]>") // < and > print < > in HTML without using them as tags
diff --git a/code/modules/discord/tgs_commands.dm b/code/modules/discord/tgs_commands.dm
index 63d0889ecbe..e2c92e34545 100644
--- a/code/modules/discord/tgs_commands.dm
+++ b/code/modules/discord/tgs_commands.dm
@@ -15,32 +15,3 @@
// If we got here, they arent in the list. Chuck 'em in!
SSdiscord.notify_members += sender.mention
return "You will now be notified when the server restarts"
-
-// Verify
-/datum/tgs_chat_command/verify
- name = "verify"
- help_text = "Verifies your discord account and your BYOND account linkage"
-
-/datum/tgs_chat_command/verify/Run(datum/tgs_chat_user/sender, params)
- var/lowerparams = replacetext(lowertext(params), " ", "") // Fuck spaces
- var/discordid = SSdiscord.id_clean(sender.mention)
- if(SSdiscord.account_link_cache[lowerparams]) // First if they are in the list, then if the ckey matches
- if(SSdiscord.account_link_cache[lowerparams] == discordid) // If the associated ID is the correct one
- // Link the account in the DB table
- SSdiscord.link_account(lowerparams)
- return "Successfully linked accounts"
- else
- return "That ckey is not associated to this discord account. If someone has used your ID, please inform an administrator"
- else
- return "Account not setup for linkage"
-
-
-/// Gets the discord user's Discord UserID
-/datum/tgs_chat_command/myuserid
- name = "myuserid"
- help_text = "Returns your userid"
-
-/datum/tgs_chat_command/myuserid/Run(datum/tgs_chat_user/sender, params)
- var/discordid = SSdiscord.id_clean(sender.mention)
- return "<@[discordid]> Your Discord UserID is [discordid]"
-
diff --git a/tgstation.dme b/tgstation.dme
index 00a2a482408..007f903f6ee 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1822,7 +1822,7 @@
#include "code\modules\detectivework\footprints_and_rag.dm"
#include "code\modules\detectivework\scanner.dm"
#include "code\modules\discord\accountlink.dm"
-#include "code\modules\discord\manipulation.dm"
+#include "code\modules\discord\discord_link_record.dm"
#include "code\modules\discord\tgs_commands.dm"
#include "code\modules\discord\toggle_notify.dm"
#include "code\modules\economy\_economy.dm"